UNPKG

@hashgraph/hedera-cli

Version:

CLI tool to manage and setup developer environments for Hedera Hashgraph.

543 lines (473 loc) 14 kB
import * as fs from 'fs'; import * as path from 'path'; import { baseState } from './helpers/state'; import { program } from 'commander'; import commands from '../src/commands'; import stateController from '../src/state/stateController'; import api from '../src/api'; import { Logger } from '../src/utils/logger'; import { Token } from '../types'; const logger = Logger.getInstance(); describe('End to end tests', () => { const logSpy = jest.spyOn(logger, 'log'); beforeEach(() => { stateController.saveState(baseState); // reset state to base state for each test }); afterEach(async () => { // Reset state to base state const distStatePath = path.join( __dirname, '..', 'dist', 'state', 'state.json', ); await fs.writeFileSync( distStatePath, JSON.stringify(baseState, null, 2), 'utf8', ); }); afterAll(() => { stateController.saveState(baseState); logSpy.mockClear(); }); /** * E2E testing flow: * - Setup init * - Switch to localnet * - Create a new account with specific balance, type, and network and verify it is created * - Transfer part of the balance back to the operator account and verify the balance is correct * - Create a backup of the state file and verify it is created * - Delete the account and verify it is deleted * - Restore the state file from backup and verify the account and operator details are restored */ test('✅ Flow 1', async () => { // Arrange: Setup init commands.setupCommands(program); // Act await program.parseAsync(['node', 'hedera-cli.ts', 'setup', 'init']); // Assert const accounts = stateController.get('accounts'); expect(accounts['localnet-operator']).toBeDefined(); // Arrange: Change network to localnet commands.networkCommands(program); // Act await program.parseAsync(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); // Assert const network = stateController.get('network'); expect(network).toEqual('localnet'); // Arrange: Create a new account with specific balance, type, and network and verify it is created commands.accountCommands(program); commands.waitCommands(program); const accountAlias = 'greg'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAlias, '-t', 'ECdsA', '--auto-associations', '1', ]); await new Promise((resolve) => setTimeout(resolve, 7000)); // Assert let state = stateController.get('accounts'); expect(state[accountAlias]).toBeDefined(); expect(state[accountAlias].type).toEqual('ECDSA'); let data = await api.account.getAccountInfo(state[accountAlias].accountId); expect(data.data.balance.balance).toEqual(10000); // default value if no balance is specified // Arrange: Transfer part of the balance back to the operator account and verify the balance is correct commands.hbarCommands(program); const transferAmount = 0.00001; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'hbar', 'transfer', '-b', transferAmount.toString(), '-f', accountAlias, '-t', 'localnet-operator', ]); await new Promise((resolve) => setTimeout(resolve, 7000)); // Assert data = await api.account.getAccountInfo(state[accountAlias].accountId); expect(data.data.balance.balance).toEqual(9000); // Arrange: Create a backup of the state file and verify it is created commands.backupCommands(program); const backupName = 'e2e'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'backup', 'create', '--name', backupName, ]); // Assert let files = fs.readdirSync(path.join(__dirname, '..', 'src', 'state')); expect(files).toContain(`state.backup.${backupName}.json`); // Arrange: Delete the account and verify it is deleted in state commands.accountCommands(program); // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'delete', '-a', accountAlias, ]); // Assert state = stateController.get('accounts'); expect(state[accountAlias]).toBeUndefined(); // Arrange: Restore the state file from backup and verify the account and operator details are restored commands.backupCommands(program); // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'backup', 'restore', '-f', `state.backup.${backupName}.json`, ]); // Assert state = stateController.get('accounts'); expect(state[accountAlias]).toBeDefined(); // Cleanup files = fs.readdirSync(path.join(__dirname, '..', 'src', 'state')); const pattern = /^state\.backup\.[a-zA-Z0-9]+\.json$/; const backups = files.filter((file) => pattern.test(file)); for (const backup of backups) { fs.unlinkSync(path.join(__dirname, '..', 'src', 'state', backup)); } }, 60000); /** * E2E testing flow for scripts: * - Download a script from the internet * - Load and execute the script (list all scripts and spy on logger function) * - Delete script and verify it is deleted in state file */ test('✅ Script features', async () => { // Arrange: Download a script from the internet commands.stateCommands(program); const scriptURL = 'https://raw.githubusercontent.com/hashgraph/hedera-cli/main/src/commands/script/examples.json'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'state', 'download', '--url', scriptURL, ]); // Assert let scripts = stateController.get('scripts'); expect(scripts['script-token']).toBeDefined(); expect(scripts['script-account-create']).toBeDefined(); // Arrange: Load and execute the script (list all scripts and spy on logger function) commands.scriptCommands(program); // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'script', 'load', '-n', 'account-create-simple', ]); // Assert const distStatePath = path.join( __dirname, '..', 'dist', 'state', 'state.json', ); // read state from dist/state/state.json because script load uses dist/ logic const distState = await fs.readFileSync(distStatePath, 'utf8'); expect(Object.keys(JSON.parse(distState).accounts).length).toBe(1); // 1 random account created // Reset state to base state await fs.writeFileSync( distStatePath, JSON.stringify(baseState, null, 2), 'utf8', ); // Arrange: Delete script and verify it is deleted in state file // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'script', 'delete', '-n', 'account-create-simple', ]); // Assert scripts = stateController.get('scripts'); expect(scripts['script-account-create-simple']).toBeUndefined(); expect(scripts['script-account-create']).toBeDefined(); }); /** * E2E testing flow for tokens: * - Create 3 accounts * - Create a token (account 1 is the treasury and account 2 is the admin key) * - Associate the token with account 3 * - Transfer 1 unit of token from treasury to account 3 * - Verify balance by looking up the token balance of account 3 via API */ test('✅ Token features', async () => { // Arrange: Create 3 accounts commands.accountCommands(program); const accountAliasTreasury = 'treasury'; const accountAliasAdmin = 'admin'; const accountAliasUser = 'user'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAliasTreasury, '-b', '300000000', ]); await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAliasAdmin, '-b', '300000000', ]); await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAliasUser, '-b', '300000000', ]); // Assert const accounts = stateController.get('accounts'); expect(accounts[accountAliasTreasury]).toBeDefined(); expect(accounts[accountAliasAdmin]).toBeDefined(); expect(accounts[accountAliasUser]).toBeDefined(); // Arrange: Create a token (account 1 is the treasury and account 2 is the admin key) commands.tokenCommands(program); const tokenName = 'test-token'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'token', 'create', '-a', accounts[accountAliasAdmin].privateKey, '-t', accounts[accountAliasTreasury].accountId, '-k', accounts[accountAliasTreasury].privateKey, '-n', tokenName, '-s', 'TT', '-i', '1000', '-d', '2', '--supply-type', 'infinite', ]); // Assert let tokens = stateController.get('tokens') as Token[]; let token = Object.values(tokens).find( (token: Token) => token.name === tokenName, ); expect(token).toBeDefined(); // TypeScript still sees `token` as possibly undefined. You need to assert it's not. if (!token) { throw new Error('Token not found'); } // Arrange: Associate the token with account 3 (user) // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'token', 'associate', '--account-id', accounts[accountAliasUser].accountId, '-t', token.tokenId, ]); // Assert tokens = stateController.get('tokens') as Token[]; token = Object.values(tokens).find( (token: Token) => token.name === tokenName, ); expect(token?.associations).toEqual([ { accountId: accounts[accountAliasUser].accountId, alias: accountAliasUser, }, ]); if (!token) { throw new Error('Token not found'); } // Arrange: Transfer 1 unit of token from treasury to account 3 (user) // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'token', 'transfer', '-t', token.tokenId, '-b', '1', '--from', accountAliasTreasury, '--to', accountAliasUser, ]); await new Promise((resolve) => setTimeout(resolve, 7000)); // Assert const data = await api.token.getTokenBalance( token.tokenId, accounts[accountAliasUser].accountId, ); expect(data.data.balances).toEqual([ { account: accounts[accountAliasUser].accountId, balance: 1, decimals: 2, }, ]); }); /** * E2E testing flow for topics: * - Create a topic with admin key and submit key * - Submit a message to topic (submit key should sign) * - Find the message and verify it is correct */ test('✅ Topic features', async () => { // Arrange: Setup init commands.setupCommands(program); // Act await program.parseAsync(['node', 'hedera-cli.ts', 'setup', 'init']); // Assert let accounts = stateController.get('accounts'); expect(accounts['localnet-operator']).toBeDefined(); // Arrange: Change network to localnet commands.networkCommands(program); // Act await program.parseAsync(['node', 'hedera-cli.ts', 'network', 'use', 'localnet']); // Assert const network = stateController.get('network'); expect(network).toEqual('localnet'); // Arrange: Create 2 accounts commands.accountCommands(program); const accountAliasAdmin = 'admin'; const accountAliasSubmit = 'submit'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAliasAdmin, '-b', '300000000', ]); await program.parseAsync([ 'node', 'hedera-cli.ts', 'account', 'create', '-a', accountAliasSubmit, '-b', '300000000', ]); // Assert accounts = stateController.get('accounts'); expect(accounts[accountAliasAdmin]).toBeDefined(); expect(accounts[accountAliasSubmit]).toBeDefined(); // Arrange: Create a topic with admin key and submit key commands.topicCommands(program); const topicMemo = 'test-topic'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'topic', 'create', '--memo', topicMemo, '-a', accounts[accountAliasAdmin].privateKey, '-s', accounts[accountAliasSubmit].privateKey, ]); // Assert let topics = stateController.get('topics'); expect(Object.keys(topics).length).toBe(1); expect(topics[Object.keys(topics)[0]].memo).toEqual(topicMemo); // Arrange: Submit a message to topic (submit key should sign) const message = 'Hello world!'; // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'topic', 'message', 'submit', '-m', message, '-t', Object.keys(topics)[0], ]); await new Promise((resolve) => setTimeout(resolve, 7000)); // Assert const response = await api.topic.findMessage(Object.keys(topics)[0], 1); // first message expect( Buffer.from(response.data.message, 'base64').toString('ascii'), ).toEqual(message); // decode buffer // Arrange: Find the message and verify it is correct // Act await program.parseAsync([ 'node', 'hedera-cli.ts', 'topic', 'message', 'find', '-t', Object.keys(topics)[0], '-s', '1', ]); // Assert expect(logSpy).toHaveBeenCalledWith( `Message found: "${Buffer.from(response.data.message, 'base64').toString( 'ascii', )}"`, ); }); });