diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1fc2e1f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Coverage Diff + +on: + push: + branches: + - master + pull_request: {} + +jobs: + test: + name: Coverage Diff + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 20 + cache: yarn + - run: yarn install + - run: yarn run test:coverage + - name: Coverage Diff + uses: greatwizard/coverage-diff-action@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/jest.config.js b/jest.config.js index 27a2b24..53e59ea 100644 --- a/jest.config.js +++ b/jest.config.js @@ -30,17 +30,10 @@ export default { coverageDirectory: 'coverage', // An array of regexp pattern strings used to skip coverage collection - // coveragePathIgnorePatterns: [ - // "/node_modules/" - // ], + coveragePathIgnorePatterns: ['/node_modules/', '/src/utils/constants.js', '/src/command/index.js'], // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], + coverageReporters: ['text', 'json-summary'], // An object that configures minimum threshold enforcement for coverage results // coverageThreshold: null, @@ -143,10 +136,13 @@ export default { // testLocationInResults: false, // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], + testMatch: [ + // '**/test/utils/Logger.test.js' + '**/test/command/dappServer/socket-sign.test.js', + '**/test/**/?(*.)+(spec|test).[jt]s?(x)' + // "**/?(*.)+(spec|test).[tj]s?(x)" + ], + testTimeout: 20000, // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // testPathIgnorePatterns: [ @@ -176,7 +172,7 @@ export default { // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation transformIgnorePatterns: [ // "/node_modules/(?!chalk|update-notifier|configstore|xdg-basedir|unique-string|crypto-random-string|semver-diff|latest-version|package-json|got|@sindresorhus|p-cancelable|@szmarczak/http-timer|cacheable-request|normalize-url|responselike|lowercase-keys|mimic-response|form-data-encoder)" - ], + ] // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, diff --git a/package.json b/package.json index 19c872a..4f5cef9 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "jest": "^29.7.0", "lint-staged": "^15.2.5", "prettier": "^3.3.2", + "socket.io-client": "^4.7.5", "standard-version": "^9.5.0" }, "keywords": [ diff --git a/src/command/dappServer/socket.js b/src/command/dappServer/socket.js index 8254888..cf0d64c 100644 --- a/src/command/dappServer/socket.js +++ b/src/command/dappServer/socket.js @@ -251,10 +251,11 @@ class Socket { } serializeResult(appId, result) { - if (!this.clientConfig[appId]) { - throw new Error(`AppId ${appId} has not connected`); - } - if (this.clientConfig[appId].encryptWay === 'sign') { + // delete next line as function deserializeParams already has the logic + // if (!this.clientConfig[appId]) { + // throw new Error(`AppId ${appId} has not connected`); + // } + if (this.clientConfig[appId]?.encryptWay === 'sign') { const originalResult = serializeMessage(result); const signature = this.clientConfig[appId].encrypt.sign(Buffer.from(originalResult, 'base64')); return { @@ -263,7 +264,7 @@ class Socket { }; } const originalResult = serializeMessage(result); - return this.clientConfig[appId].encrypt.encrypt(originalResult); + return this.clientConfig[appId]?.encrypt.encrypt(originalResult); } async handleConnect(message) { @@ -362,7 +363,7 @@ class Socket { }; } - async handleInvoke(message, isReadOnly = false) { + async handleInvoke(message, isReadOnly) { const params = await this.deserializeParams(message); const { endpoint = this.defaultEndpoint, contractAddress, contractMethod, arguments: contractArgs } = params; logger.info(`${isReadOnly ? 'Calling' : 'Sending'} contract ${contractAddress} method ${contractMethod}...`); diff --git a/src/utils/utils.js b/src/utils/utils.js index c6eebdb..969aaf9 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -138,6 +138,7 @@ async function promptTolerateSeveralTimes({ processAfterPrompt = () => {}, patte askTimes++; } catch (e) { oraInstance.fail('Failed'); + break; } } if (askTimes >= times && answerInput === null) { @@ -289,7 +290,6 @@ async function getParams(method) { } catch (e) {} let paramValue; // todo: use recursion - if ( rule !== 'repeated' && innerType && @@ -366,7 +366,7 @@ async function deserializeLogs(aelf, logs = []) { let results = await Promise.all(logs.map(v => getProto(aelf, v.Address))); results = results.map((proto, index) => { const { Name, NonIndexed, Indexed = [] } = logs[index]; - const serializedData = [...(Indexed || [])]; + const serializedData = [...Indexed]; if (NonIndexed) { serializedData.push(NonIndexed); } diff --git a/test/command/call.test.js b/test/command/call.test.js index d806aeb..e4e52af 100644 --- a/test/command/call.test.js +++ b/test/command/call.test.js @@ -1,12 +1,13 @@ import { Command } from 'commander'; import path from 'path'; import AElf from 'aelf-sdk'; +import inquirer from 'inquirer'; import { CallCommand } from '../../src/command'; import { callCommandUsages, callCommandParameters } from '../../src/utils/constants.js'; import { getContractInstance } from '../../src/utils/utils.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger.js'; -import inquirer from 'inquirer'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; const sampleRc = { getConfigs: jest.fn() }; jest.mock('../../src/utils/myLogger'); @@ -14,13 +15,10 @@ jest.mock('../../src/utils/myLogger'); describe('CallCommand', () => { let callCommand; let mockOraInstance; - const endPoint = 'https://tdvw-test-node.aelf.io/'; const aelf = new AElf(new AElf.providers.HttpProvider(endPoint)); const wallet = AElf.wallet.getWalletByPrivateKey('943df6d39fd1e1cc6ae9813e54f7b9988cf952814f9c31e37744b52594cb4096'); const address = 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); + beforeEach(() => { backup = inquirer.prompt; mockOraInstance = { @@ -48,12 +46,12 @@ describe('CallCommand', () => { const result = await callCommand.callMethod(method, params); expect(mockOraInstance.start).toHaveBeenCalledWith('Calling method...'); expect(mockOraInstance.succeed).toHaveBeenCalledWith('Calling method successfully!'); - }, 20000); + }); test('should process address after prompt', async () => { const answerInput = { contractAddress: address }; const result = await callCommand.processAddressAfterPrompt(aelf, wallet, answerInput); expect(result.address).toBe(address); - }, 20000); + }); test('should run with valid inputs', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -73,7 +71,7 @@ describe('CallCommand', () => { }) ); expect(logger.info).toHaveBeenCalled(); - }, 20000); + }); test('should run without contractAddress', async () => { inquirer.prompt = questions => Promise.resolve(''); const commander = new Command(); @@ -87,7 +85,7 @@ describe('CallCommand', () => { commander.parse([process.argv[0], '', 'call', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await callCommand.run(commander); expect(logger.fatal).toHaveBeenCalled(); - }, 5000); + }); test('should run without params', async () => { inquirer.prompt = questions => Promise.resolve({ symbol: 'ELF' }); @@ -102,7 +100,7 @@ describe('CallCommand', () => { commander.parse([process.argv[0], '', 'call', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await callCommand.run(commander, 'AElf.ContractNames.Token', 'GetTokenInfo'); expect(logger.info).toHaveBeenCalled(); - }, 20000); + }); test('should run with invalid parameters', async () => { inquirer.prompt = backup; @@ -133,7 +131,7 @@ describe('CallCommand', () => { }) ); expect(logger.info).toHaveBeenCalled(); - }, 20000); + }); afterEach(() => { inquirer.prompt = backup; diff --git a/test/command/config.test.js b/test/command/config.test.js index 5442cc6..7ac7740 100644 --- a/test/command/config.test.js +++ b/test/command/config.test.js @@ -19,7 +19,7 @@ describe('ConfigCommand', () => { deleteConfig: jest.fn() }; const endPoint = 'https://tdvw-test-node.aelf.io/'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); + const dataDir = path.resolve(__dirname, '../dataDir/aelf'); beforeEach(() => { mockOraInstance = { start: jest.fn(), diff --git a/test/command/console.test.js b/test/command/console.test.js index cd49775..7cb01e2 100644 --- a/test/command/console.test.js +++ b/test/command/console.test.js @@ -5,6 +5,7 @@ import { Command } from 'commander'; import ConsoleCommand from '../../src/command/console.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('boxen'); jest.mock('repl'); @@ -16,10 +17,6 @@ describe('ConsoleCommand', () => { const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstance = { succeed: jest.fn(), @@ -46,7 +43,7 @@ describe('ConsoleCommand', () => { await consoleCommand.run(commander); expect(oraInstance.succeed).toHaveBeenCalledWith('Succeed!'); expect(logger.info).toHaveBeenCalledTimes(2); - }, 20000); + }); test('should handle errors correctly', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -63,5 +60,5 @@ describe('ConsoleCommand', () => { await consoleCommand.run(commander); expect(oraInstance.fail).toHaveBeenCalledWith('Failed!'); expect(logger.error).toHaveBeenCalled(); - }, 20000); + }); }); diff --git a/test/command/create.test.js b/test/command/create.test.js index d41137d..96e7f52 100644 --- a/test/command/create.test.js +++ b/test/command/create.test.js @@ -4,6 +4,7 @@ import CreateCommand from '../../src/command/create.js'; import { saveKeyStore } from '../../src/utils/wallet'; import { logger } from '../../src/utils/myLogger'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/wallet'); jest.mock('../../src/utils/myLogger'); @@ -12,10 +13,6 @@ describe('CreateCommand', () => { let createCommand; let oraInstance; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstance = { succeed: jest.fn(), diff --git a/test/command/dappServer/HKDF.test.js b/test/command/dappServer/HKDF.test.js new file mode 100644 index 0000000..a993c6c --- /dev/null +++ b/test/command/dappServer/HKDF.test.js @@ -0,0 +1,29 @@ +import HKDF from '../../../src/command/dappServer/HKDF'; + +describe('HKDF', () => { + const hash = 'sha256'; + const random = '52695e07c0c545e8a279a71b1f47b9b7'; + const salt = Buffer.from(random, 'hex'); + console.log(salt, 'salt'); + const initialKey = '667a8937f4da939b93e716211e83f787741be3fe0c938bea9e333d37c779b5'; + afterEach(() => { + jest.clearAllMocks(); + }); + test('should initialize with correct values', () => { + const hkdf = new HKDF(hash, salt, initialKey); + expect(hkdf.hashMethod).toBe(hash); + expect(hkdf.hashLength).toBe(32); + expect(hkdf.salt).toBe(salt); + expect(hkdf.initialKey).toBe(initialKey); + console.log(hkdf.prk, 'hkdf.prk'); + expect(hkdf.prk.toString('hex')).toEqual('6269eb093d3ed47cd698158c8f404b6380cd3222f3889323ea7814ea341456f2'); + }); + test('should throw an error for unsupported hash method', () => { + expect(() => new HKDF('sha1', Buffer.from('salt'), 'initialKey')).toThrow('not supported hash method'); + }); + test('should expand correctly', () => { + const hkdf = new HKDF(hash, salt, initialKey); + const result = hkdf.expand(); + expect(result.toString('hex')).toBe('50649277a8ec2090de2c15af4aab197a7baa3c09accb755d3fafa829c370ba62'); + }); +}); diff --git a/test/command/dappServer/encrypt.test.js b/test/command/dappServer/encrypt.test.js new file mode 100644 index 0000000..663ff92 --- /dev/null +++ b/test/command/dappServer/encrypt.test.js @@ -0,0 +1,65 @@ +/* eslint-disable max-len */ +import elliptic from 'elliptic'; +import Encrypt from '../../../src/command/dappServer/encrypt'; +import { randomId } from '../../../src/utils/utils.js'; + +jest.mock('../../../src/utils/utils.js', () => { + return { + randomId: () => '1ef39a09ce4f415da335dcb408989116' + }; +}); + +describe('Encrypt', () => { + const algorithm = 'curve25519'; + const remotePublicKey = '75d5e81323eecc4e887ef0934a52d8de1a2785ca04f4a4e9a39359c4bfd8cc9d'; + const random = 'e44ab14618724a80be03f4565e7ed668'; + const ecInstance = new Encrypt(algorithm, remotePublicKey, random); + const data = + 'JTdCJTIyY29kZSUyMiUzQTAlMkMlMjJtc2clMjIlM0ElMjJzdWNjZXNzJTIyJTJDJTIyZXJyb3IlMjIlM0ElNUIlNUQlMkMlMjJkYXRhJTIyJTNBJTdCJTIyYWNjb3VudHMlMjIlM0ElNUIlN0IlMjJuYW1lJTIyJTNBJTIyYWVsZi1jb21tYW5kJTIyJTJDJTIyYWRkcmVzcyUyMiUzQSUyMkd5UVg2dDE4a3B3YUQ5WEhYZTFUb0t4Zm92OG1TZVRMRTlxOU53VUFlVEU4dFVMWmslMjIlMkMlMjJwdWJsaWNLZXklMjIlM0ElMjIwNDcwM2JiZTk1ZTk4NmM5ZDkwMWYyOGVkZDYwOTc1YTdhNmMzYjJkY2U0MWRmZWMyZTc5ODNkMjkzYzYwMGU4MjQ5NjQyYTNkYTM3OWM0MTk0YTZkNjJiZDg5YWZlNjc1M2U4MWFjZmMyYjZiYmYzYjQwNzM2ZWUwOTQ5MTAyMDcxJTIyJTdEJTVEJTJDJTIyY2hhaW5zJTIyJTNBJTVCJTdCJTIydXJsJTIyJTNBJTIyaHR0cHMlM0ElMkYlMkZ0ZHZ3LXRlc3Qtbm9kZS5hZWxmLmlvJTJGJTIyJTJDJTIyaXNNYWluQ2hhaW4lMjIlM0F0cnVlJTJDJTIyY2hhaW5JZCUyMiUzQSUyMkFFTEYlMjIlN0QlNUQlN0QlN0Q='; + const encrypted = + 'U5E3LMWOVAintuE4zJf+8O4XSHgAtZTeQX831sc53r9yZJGnh6fiwNR3tF8zCORnwtVUaYh+pnzPgxeQz8b1tKEUhc/VYoy2dtqMubMDq+WUeF5tFnFhv6kuhHkMSDYQnpwOpwSvzJ4kWr+cxdHGOg+qoWe1nPujQlc/gQ/7Z4MeEHWXDagtKXzKGfE/rYJhnlAN+K1xHhq//tS4g4izEuHe2J2RK5xYa91e5p1qzLTEQyqax2q1pyYFPbaRyrP/6yAlUPOkdyuIGdMXwEp7BdfPXF3xPemaj6w+WgOkmAUcKk35blQvNLuJAWux6ZhLl+P+w6sfinT3Mk1ymrz2SEdThjB/LEPbbBlx+in80y0S8bSgUpsXbW1mfMPX3svDks0cK3Thjf/o+sGed5Ej4cFMFbgeCI4JdDnKeG7RWPWN9lL77kzPq0q60zXr/70jyRElktzSWzGfcOEr+TQiEx3MfiRHgAoqz5glc/ml7VlUP9ouLvRQfmrWcYIsbkf9WnBac/G68rSGOEqR/qbugsQCclO+V6yJt3o4NL94Nbn8V/+gQo430zdefkljGum84c0e2vVYxXyRbrFIAROJeO4IRiVWAaLrnFc0h/Shtt8EupaWGYJps+aYODUpfulJjDm6pZoDE+bp5h02XV0C/akhIUclLveNJc14qYc7tGpSRBDNXC/z6PfZgPlV3OL+'; + const iv = '1ef39a09ce4f415da335dcb408989116'; + afterEach(() => { + jest.clearAllMocks(); + }); + test('should initialize correctly', () => { + expect(ecInstance).toHaveProperty('keyPair'); + expect(ecInstance).toHaveProperty('cipher'); + expect(ecInstance).toHaveProperty('remoteKeyPair'); + expect(ecInstance).toHaveProperty('sharedKey'); + expect(ecInstance).toHaveProperty('derivedKey'); + }); + + test('should ecInstance data correctly', () => { + // mock + ecInstance.sharedKey = Buffer.from('7f8e6db3591d76846d8b6ffefe5e0a19f85feee2cb5d41131dac2b9a668b41ca', 'hex'); + ecInstance.derivedKey = Buffer.from('0369788fa050c720131722efb25281f444857db4b833ea89ede08a8fdf6117c0', 'hex'); + const result = ecInstance.encrypt(data); + expect(result).toEqual({ + encryptedResult: encrypted, + iv + }); + }); + test('should decrypt data correctly', () => { + const decrypt = ecInstance.decrypt(encrypted, iv); + expect(decrypt).toBe(data); + }); + test('should return correct public key', () => { + const result = ecInstance.getPublicKey(); + expect(result.length).toBe(64); + }); + + test(`should initialize with not default algorithm`, () => { + // another algorithm + const ecInstanceNotDefault = new Encrypt( + 'secp256k1', + '04695fb2e8ce837d5b9e79df046dd1947a558b884165c8f83a9e9b01e47a37135fc4ff42256e4a79f25f740a840b58f47f79a3bf934857c7397e545163cddf663e', + 'beae60451b554891bb8967d0c2bdaa4e' + ); + expect(ecInstance).toHaveProperty('keyPair'); + expect(ecInstance).toHaveProperty('cipher'); + expect(ecInstance).toHaveProperty('remoteKeyPair'); + expect(ecInstance).toHaveProperty('sharedKey'); + expect(ecInstance).toHaveProperty('derivedKey'); + }); +}); diff --git a/test/command/dappServer/index.test.js b/test/command/dappServer/index.test.js new file mode 100644 index 0000000..ee63e48 --- /dev/null +++ b/test/command/dappServer/index.test.js @@ -0,0 +1,57 @@ +import { Command } from 'commander'; +import { userHomeDir } from '../../../src/utils/userHomeDir.js'; +import DeployCommand from '../../../src/command/dappServer/index'; +import Socket from '../../../src/command/dappServer/socket'; +import { logger } from '../../../src/utils/myLogger'; +import { endpoint as endPoint, account, password, dataDir } from '../../constants.js'; + +jest.mock('../../../src/command/dappServer/socket'); +jest.mock('../../../src/utils/myLogger'); +describe('DeployCommand', () => { + let deployCommand; + let oraInstanceMock; + const sampleRc = { getConfigs: jest.fn() }; + beforeEach(() => { + oraInstanceMock = { + start: jest.fn(), + clear: jest.fn(), + succeed: jest.fn(), + fail: jest.fn() + }; + deployCommand = new DeployCommand(sampleRc); + deployCommand.oraInstance = oraInstanceMock; + }); + afterEach(() => { + jest.clearAllMocks(); + }); + test('should run and start the server successfully', async () => { + const commander = new Command(); + commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); + commander.option('-a, --account ', 'The address of AElf wallet'); + commander.option('-p, --password ', 'The password of encrypted keyStore'); + commander.option( + '-d, --datadir ', + `The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf` + ); + commander.parse([process.argv[0], '', 'wallet', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); + await deployCommand.run(commander); + expect(logger.info).toHaveBeenCalledWith('DApp server is listening on port 35443'); + }); + test('should handle errors during server startup', async () => { + Socket.mockImplementation(_ => { + throw new Error('socket error'); + }); + const commander = new Command(); + commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); + commander.option('-a, --account ', 'The address of AElf wallet'); + commander.option('-p, --password ', 'The password of encrypted keyStore'); + commander.option( + '-d, --datadir ', + `The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf` + ); + commander.parse([process.argv[0], '', 'wallet', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); + await deployCommand.run(commander); + expect(oraInstanceMock.fail).toHaveBeenCalledWith('Failed!'); + expect(logger.error).toHaveBeenCalled(); + }); +}); diff --git a/test/command/dappServer/sign.test.js b/test/command/dappServer/sign.test.js new file mode 100644 index 0000000..668a34c --- /dev/null +++ b/test/command/dappServer/sign.test.js @@ -0,0 +1,48 @@ +/* eslint-disable max-len */ +import elliptic from 'elliptic'; +import Sign from '../../../src/command/dappServer/sign'; + +describe('Sign', () => { + const algorithm = 'secp256k1'; + const remotePublicKey = + '04a6eba849ac6cbd7f61df41fee59f46540b3d969da6f859d6ccbac9cdc9af94e10045b52074ba4c154dd3cc395c45b757e3e515740d67fc5e7b6215ec7cd281fb'; + const msg = Buffer.from('31373138393634323633', 'hex'); + const signature = + 'b8b201b3599ea04b196c836697d6fa42a5841cf42a6b4a22394022afed0fd41274f25b0f5520efbcc98834640abf7f83508a3a8904e992a8324ad8cc59ff3da200'; + const signInstance = new Sign(algorithm, remotePublicKey); + afterEach(() => { + jest.clearAllMocks(); + }); + test('should initialize correctly', () => { + expect(signInstance).toHaveProperty('keyPair'); + expect(signInstance).toHaveProperty('remoteKeyPair'); + }); + + test('should verify data correctly', () => { + const result = Sign.verify(algorithm, remotePublicKey, msg, signature); + expect(result).toEqual(true); + }); + test('should sign msg correctly', () => { + const data = Buffer.from('4811b3d9e39b4214af728436159f7387', 'hex'); + const result = signInstance.sign(data); + expect(result.length).toBe(130); + }); + test('should verify msg in fixed algorithm and publicKey', () => { + const msg = Buffer.from('25374225323274696d657374616d7025323225334131373138393635333132253744', 'hex'); + const signature = + '8904880db5ecb7bc4b5b09046cb359dbc78c9cf507f5445244b24bdcec7def1599393bf98ad27178c6052a6e2b95d4393dceac7420e776544797f5e2f74d7ac800'; + const result = signInstance.verify(msg, signature); + expect(result).toBe(true); + }); + test('should return correct public key', () => { + const result = signInstance.getPublicKey(); + expect(result.length).toBe(130); + }); + + test(`should initialize with not default algorithm`, () => { + // another algorithm + const signInstanceNotDefault = new Sign('curve25519', '75d5e81323eecc4e887ef0934a52d8de1a2785ca04f4a4e9a39359c4bfd8cc9d'); + expect(signInstance).toHaveProperty('keyPair'); + expect(signInstance).toHaveProperty('remoteKeyPair'); + }); +}); diff --git a/test/command/dappServer/socket-sign.test.js b/test/command/dappServer/socket-sign.test.js new file mode 100644 index 0000000..2e782e8 --- /dev/null +++ b/test/command/dappServer/socket-sign.test.js @@ -0,0 +1,338 @@ +/* eslint-disable max-len */ +import ioc from 'socket.io-client'; +import AElf from 'aelf-sdk'; +import { getWallet } from '../../../src/utils/wallet'; +import Socket from '../../../src/command/dappServer/socket'; +import { logger } from '../../../src/utils/myLogger'; +import * as utils from '../../../src/command/dappServer/utils'; +import Sign from '../../../src/command/dappServer/sign'; +import { endpoint, account, password, dataDir } from '../../constants.js'; + +jest.mock('../../../src/command/dappServer/sign'); +jest.mock('../../../src/utils/myLogger'); + +const connectData = { + action: 'connect', + params: { + publicKey: + '04b00b9a0c0359a5e0a55b0efa32469929765b30bc5a8b375d2dbffa43322f87df3401845a4cc3841a36c3f14a7481ce1c4e9d920f18ede0f4bcbd29e291a4190a', + timestamp: 1718870995, + encryptAlgorithm: 'secp256k1', + signature: + '33977ec3965229628feb4b95846442d71d20fa9bfd54efe6958daa5fdf369a3ecde738f9a10374dd8b56064618e50746b9c3581ade80abfc3b69decadadec7a200' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '4b62bc590a4d43beb432c1692e1a2234' +}; +let clientSocket, + socketInstance, + port = 35444; +const serverUrl = `http://localhost:${port}`; +const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); +const wallet = getWallet(dataDir, account, password); + +describe('Socket Server with sign', () => { + beforeEach(done => { + socketInstance = new Socket({ + port, + endpoint, + aelf, + wallet, + address: account + }); + clientSocket = ioc(serverUrl, { + transports: ['websocket'], + timeout: 5000, + reconnectionAttempts: 5 + }); + clientSocket.on('connect', async message => { + await new Promise(resolve => setTimeout(resolve, 1000)); + done(); + }); + }); + + afterEach(() => { + clientSocket.close(); + socketInstance.socket.close(); + }); + test('should handle invalid signature', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('Not a valid signature')); + done(); + }); + + Sign.verify = jest.fn().mockReturnValue(false); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + '5a54b642b72af76fdd707fc00f7b48a6166e0816240f1fb5528777c55289e0c7c94eb88e67d9a07f8b5c99777ecbbb99d5482147ef7766888c29c837b023afd101', + getPublicKey: () => + '04e8feb5a19d6de284218083c75b16bdf9bc356e46c2c6da7ec5f9a96ae150d194bfae36782ae54094e4a3be41dd1f92a8eef2e42b0af82511795d25f37a6945c0' + }; + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + clientSocket.emit('bridge', connectData); + }); + + test('should handle invalid Timestamp', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('Timestamp is not valid')); + done(); + }); + + Sign.verify = jest.fn().mockReturnValue(true); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + '5a54b642b72af76fdd707fc00f7b48a6166e0816240f1fb5528777c55289e0c7c94eb88e67d9a07f8b5c99777ecbbb99d5482147ef7766888c29c837b023afd101', + getPublicKey: () => + '04e8feb5a19d6de284218083c75b16bdf9bc356e46c2c6da7ec5f9a96ae150d194bfae36782ae54094e4a3be41dd1f92a8eef2e42b0af82511795d25f37a6945c0' + }; + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(false); + clientSocket.emit('bridge', connectData); + }); + test('should handle not support encrypt method or not enough params', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith(new Error('Not support encrypt method or not enough params')); + expect(logger.error).toHaveBeenCalledWith('error happened'); + done(); + }); + const invalidConnectData = { + action: 'connect', + params: { + publicKey: + '04b00b9a0c0359a5e0a55b0efa32469929765b30bc5a8b375d2dbffa43322f87df3401845a4cc3841a36c3f14a7481ce1c4e9d920f18ede0f4bcbd29e291a4190a', + timestamp: 1718870995, + encryptAlgorithm: 'secp256k1' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '4b62bc590a4d43beb432c1692e1a2234' + }; + clientSocket.emit('bridge', invalidConnectData); + }); +}); + +describe('Socket Server with sign', () => { + beforeEach(done => { + socketInstance = new Socket({ + port, + endpoint, + aelf, + wallet, + address: account + }); + clientSocket = ioc(serverUrl, { + transports: ['websocket'], + timeout: 5000, + reconnectionAttempts: 5 + }); + clientSocket.on('connect', async message => { + await new Promise(resolve => setTimeout(resolve, 1000)); + done(); + }); + Sign.verify = jest.fn().mockReturnValue(true); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + '5a54b642b72af76fdd707fc00f7b48a6166e0816240f1fb5528777c55289e0c7c94eb88e67d9a07f8b5c99777ecbbb99d5482147ef7766888c29c837b023afd101', + getPublicKey: () => + '04e8feb5a19d6de284218083c75b16bdf9bc356e46c2c6da7ec5f9a96ae150d194bfae36782ae54094e4a3be41dd1f92a8eef2e42b0af82511795d25f37a6945c0' + }; + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + clientSocket.emit('bridge', connectData); + }); + + afterEach(() => { + clientSocket.close(); + socketInstance.socket.close(); + }); + + test('should handle account action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith('Querying account information'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + 'd319351e191c8a5f751ed3b3f586af53017dba17f5ec58980ba51ec51f968a3cc8deadb4e9acbb8ddd84b065a8b919d2d32724e87d7df6f412581fa3d375523e01' + }; + }); + const data = { + action: 'account', + params: { + signature: + '8747f44c44d468554fcaeadb64a3cd75444c2bbb91c8e52855679bebc1ca9282a6499911b92da3d627a5b03788c6b5817a61b4183a0e5d7535c2af0abce11a4e01', + originalParams: 'JTdCJTIydGltZXN0YW1wJTIyJTNBMTcxODg3MDk5NiU3RA==' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: 'cd78bea8c031411fb6659939f9070527' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle api action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith('Querying api /api/blockChain/chainStatus...'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + 'd319351e191c8a5f751ed3b3f586af53017dba17f5ec58980ba51ec51f968a3cc8deadb4e9acbb8ddd84b065a8b919d2d32724e87d7df6f412581fa3d375523e01' + }; + }); + const data = { + action: 'api', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhcGlQYXRoJTIyJTNBJTIyJTJGYXBpJTJGYmxvY2tDaGFpbiUyRmNoYWluU3RhdHVzJTIyJTJDJTIybWV0aG9kTmFtZSUyMiUzQSUyMmdldENoYWluU3RhdHVzJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTVEJTJDJTIydGltZXN0YW1wJTIyJTNBMTcxODg3Mjc4MCU3RA==' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: 'e647eda5134a4325a5bf03f429336c03' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invoke action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith( + 'Sending contract ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx method Transfer...' + ); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + 'd319351e191c8a5f751ed3b3f586af53017dba17f5ec58980ba51ec51f968a3cc8deadb4e9acbb8ddd84b065a8b919d2d32724e87d7df6f412581fa3d375523e01' + }; + }); + const data = { + action: 'invoke', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJjb250cmFjdEFkZHJlc3MlMjIlM0ElMjJBU2gyV3Q3blNFbVlxbkd4UFB6cDRwblZEVTR1aGoxWFc5U2U1VmVaY1gyVURkeWp4JTIyJTJDJTIyY29udHJhY3RNZXRob2QlMjIlM0ElMjJUcmFuc2ZlciUyMiUyQyUyMmFyZ3VtZW50cyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJwYXJhbXMlMjIlMkMlMjJ2YWx1ZSUyMiUzQSU3QiUyMnN5bWJvbCUyMiUzQSUyMkVMRiUyMiUyQyUyMmFtb3VudCUyMiUzQTEwMDAwMDAwMCUyQyUyMm1lbW8lMjIlM0ElMjJ5ZWFoJTIyJTJDJTIydG8lMjIlM0ElMjIyUkNMbVpRMjI5MXhEd1NiREVKUjZuTGhGSmNNa3lmclZUcTFpMVl4V0M0U2RZNDlhNiUyMiU3RCU3RCU1RCUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NzMzMDAlN0Q=' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: 'a31b390269ad4caa8da0557c43dea556' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invokeRead action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith( + 'Calling contract 2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2 method GetContractAddressByName...' + ); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + 'd319351e191c8a5f751ed3b3f586af53017dba17f5ec58980ba51ec51f968a3cc8deadb4e9acbb8ddd84b065a8b919d2d32724e87d7df6f412581fa3d375523e01' + }; + }); + const data = { + action: 'invokeRead', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJjb250cmFjdEFkZHJlc3MlMjIlM0ElMjIyVUtRbkhjUXZoQlQ2WDZVTHRmbnVoM2I5UFZSdlZNRXJvSEhrY0s0WWZjb0gxWjF4MiUyMiUyQyUyMmNvbnRyYWN0TWV0aG9kJTIyJTNBJTIyR2V0Q29udHJhY3RBZGRyZXNzQnlOYW1lJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTdCJTIybmFtZSUyMiUzQSUyMnBhcmFtcyUyMiUyQyUyMnZhbHVlJTIyJTNBJTIyYTJhMDBmODU4M2MwOGRhYTAwYjgwYjBiYmFjNDY4NDM5NmZlOTY2YjY4M2VhOTU2YTYzYmQ4ODQ1ZWVlNmFlNyUyMiU3RCU1RCUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NzMyMjclN0Q=' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '36055f21b2d6418998dda7eda6526a2a' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle getContractMethods action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Sign.mockImplementation(() => { + return { + verify: () => true, + sign: () => + 'd319351e191c8a5f751ed3b3f586af53017dba17f5ec58980ba51ec51f968a3cc8deadb4e9acbb8ddd84b065a8b919d2d32724e87d7df6f412581fa3d375523e01' + }; + }); + const data = { + action: 'getContractMethods', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhZGRyZXNzJTIyJTNBJTIyQVNoMld0N25TRW1ZcW5HeFBQenA0cG5WRFU0dWhqMVhXOVNlNVZlWmNYMlVEZHlqeCUyMiUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NzMyMjklN0Q=' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '08400d6bac284c40a5984821c2ef4f53' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invalid signature', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith(new Error('Signature is not valid')); + done(); + }); + socketInstance.clientConfig['28e653ec-20d4-55e1-b077-a346df57666a'].encrypt.verify = () => false; + const data = { + action: 'getContractMethods', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhZGRyZXNzJTIyJTNBJTIyQVNoMld0N25TRW1ZcW5HeFBQenA0cG5WRFU0dWhqMVhXOVNlNVZlWmNYMlVEZHlqeCUyMiUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NzMyMjklN0Q=' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '08400d6bac284c40a5984821c2ef4f53' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invalid signature', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith(new Error('Timestamp is not valid')); + done(); + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(false); + const data = { + action: 'getContractMethods', + params: { + signature: + '9f6017bf0c5acee66be32d64edb63471309f7597ef175b64a1329faa3741b875cbec16a46d31a9dd69416cddb6542e3b85a974510869a597d5c099a5d1f6a95501', + originalParams: + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhZGRyZXNzJTIyJTNBJTIyQVNoMld0N25TRW1ZcW5HeFBQenA0cG5WRFU0dWhqMVhXOVNlNVZlWmNYMlVEZHlqeCUyMiUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NzMyMjklN0Q=' + }, + appId: '28e653ec-20d4-55e1-b077-a346df57666a', + id: '08400d6bac284c40a5984821c2ef4f53' + }; + clientSocket.emit('bridge', data); + }); +}); diff --git a/test/command/dappServer/socket.test.js b/test/command/dappServer/socket.test.js new file mode 100644 index 0000000..90cb3ba --- /dev/null +++ b/test/command/dappServer/socket.test.js @@ -0,0 +1,545 @@ +/* eslint-disable max-len */ +import ioc from 'socket.io-client'; +import AElf from 'aelf-sdk'; +import { getWallet } from '../../../src/utils/wallet'; +import Socket from '../../../src/command/dappServer/socket'; +import { logger } from '../../../src/utils/myLogger'; +import * as utils from '../../../src/command/dappServer/utils'; +import Encrypt from '../../../src/command/dappServer/encrypt'; +import { endpoint, account, password, dataDir } from '../../constants.js'; + +jest.mock('../../../src/command/dappServer/encrypt'); +jest.mock('../../../src/utils/myLogger'); + +describe('Socket Server', () => { + let clientSocket, + socketInstance, + port = 35443; + const serverUrl = `http://localhost:${port}`; + const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); + const wallet = getWallet(dataDir, account, password); + + beforeEach(done => { + socketInstance = new Socket({ + port, + endpoint, + aelf, + wallet, + address: account + }); + clientSocket = ioc(serverUrl, { + transports: ['websocket'], + timeout: 5000, + reconnectionAttempts: 5 + }); + clientSocket.on('connect', async message => { + await new Promise(resolve => setTimeout(resolve, 1000)); + done(); + }); + // first need to connect + const connectData = { + action: 'connect', + params: { + publicKey: '1cc7d260e79e3b83b4525cba2edd47063f8ad05a2adc229a0ff4a5716aa365e3', + encryptAlgorithm: 'curve25519', + cipher: 'aes-256-cbc' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '47bf3177c41344ca988cfedbba51cf4a' + }; + Encrypt.mockImplementation(() => { + return { + decrypt: () => 'JTdCJTIydGltZXN0YW1wJTIyJTNBMTcxODc5MjM4MCU3RA==', + encrypt: () => { + return { + id: 'a199de9ebed04676a3b0ede012854e26', + result: { + encryptedResult: + 'w7ZqPE5MYtoLVIJPjXsAd6I0sbuHQIA10PbS/d4+Iw6C62avXlcNQSjH36R/3B5+psxKGpps/buCT6yATPwB3TUI1o1NdGBpe59VIdAE6BYySvHOzWnKOiiuc4LOMD0to//YsPy5SYYkYqLVVfjdOSHBmvs7qtI1x3RN57KwDjz9s0/MJlRg9olCwWkCABqqECdE8fP9jeLnWyjyKdPE++qGF4rSnPrEpYTXCjWNjYAX2m1SqV0fcfmLCHqE+NbbTEXLtfort3ZDPAl1nr/kKEp6HkXg8hXKzYHQFv45m78PzEkyUBMS8Jsuy8tw1JPkv+TcwtdhPQemcv8aA1B/nrfVVXGPLM0MVlLOctsisiU50nq21/NhTqUR/85GolfMoDQnMXYa/GilLUiPI1ZqWeV4ZqEgZWcVS3AvEWPgZmQ4sNeaDgXFAWOBZa3bPecqqyKGAu3+LPlchLOHCqV46bH6GodrVkma2n7q92LO2Bx13TZ1v4oF/Kp4N+X1K/5BpbPOsVEyM9DfFj8+vDHY467tBOi2TvLfh46H31Jma5J8hYrghs8G9vkEsYPzVMIYhfEpw0pftrSCK9DV6AVlwJU3Pop2CTlvdBtSLNIL7Iz1SLL27Tonb93VysEAXEw+86fZZCb61+f45JExLMc21AWcdSuDmAMZGBe7mAMU/oyUjkXPC8Hv6VRmLPhcPjLY', + iv: '442e8595ba6345e39a8248993a5ca18f' + } + }; + }, + getPublicKey: () => '5074c544a996ea80d95555fef4da21a3f57673ea949293405cee975aba653b4a' + }; + }); + clientSocket.emit('bridge', connectData); + }); + + afterEach(() => { + clientSocket.close(); + socketInstance.socket.close(); + }); + + test('should handle account action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith('Querying account information'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ timestamp: 1718784774 }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + const data = { + action: 'account', + params: { + encryptedParams: 'yi4N41XoCTSz0ibXKQ2/rIYZz8D0u6vbaRxLPx+cu7AQjv3nsdWA9bqDSeQMr0ye', + iv: '67b83b89bdd44e2f8bcbb02251783825' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '281537101483488da8c12a9b02c4f563' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle api action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith('Querying api /api/blockChain/chainStatus...'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhcGlQYXRoJTIyJTNBJTIyJTJGYXBpJTJGYmxvY2tDaGFpbiUyRmNoYWluU3RhdHVzJTIyJTJDJTIybWV0aG9kTmFtZSUyMiUzQSUyMmdldENoYWluU3RhdHVzJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTVEJTJDJTIydGltZXN0YW1wJTIyJTNBMTcxODc5NDg2NyU3RA====', + encrypt: () => { + return { + id: '45b66a0738ff48a7b06cec79ff673dba', + result: { + encryptedResult: + 'WOsz8XNcMS66yp6qni9+oV9m1pelGe7uLWuICVrc4Urq1gH+XmErWMKZy7vCqPYHfS3kBVwnKMl5WHS6rEUQBZg4mPux3IfozDgNrY7NC9DN81kt/h5tKbSHSx6GMgB9GeVLH5ELj+O+ZZXSszge5k2/xRa0GZYGdX04EXG5OO4XJ+3KxE5YXmO1PhfQenZR19GynQQxymMwByRxZM0GvpKhHM3oQbq7s600wPvulHdIdXY3W4QCPyLTqQD+18aNEVzFYZjr0WGZJKVTv8NsS04gDghVIShmMCTzkvEYPM0kI9JocfSDSI5MqsMGbdpNhKAYCFBgC3ILOpbpGXG53qUUdhFcDIAnj6yeyB8ZG+pkMEBcbn+aZoWjdqS/z7UrIF0RatCKGRzZyOO8La0d+KsHOm11d/cukTsyyz9/Yvds1Equlyk9vz4BUQNkIaM2zXP6AZfE3QwoMf0IhcXdFCEoBybDNEuaxueEBVkHdM+9WF4EwqX2iRHgXa4D6qKfIGgD5zhTASWCEOJj5Q57/eSu2cZ6xT+hJiX95sO1PB0TqatQMhGz2n/fAKf5roKAXxgjAx/eiAzygTVldmMGHQtj++UveilXCeZ9BEInb3K3mRyx5pc/t874dGF44l/VqC18RK0X1xgUDYcV2ru/HJwH0OHKxDJSDlEIzME8U5x6FVGHc33kFgY1LcLEXHd+r0MpSYdtbHiwwPPTSGhgFwnpF+mWZnQepQh4VsNcfJRg8tMG9Jcx4vM9vaT7b7a9LaT333WZ1d6DiXQYZ3MJBewUwqfNlqTaz+O7o51tOXMHzX4FxcUwgsGLSgH9al5hjWb3uZZarXrF/dhB4zyQBmTdPO6k4cowcoWLvEnKiSU2mYKqSvUoqmM1o7cI21VD4reV7XQiFxUEP3CNlkNFm1imImhANZoltx+GkzZRsL9IHMgkEO5ixLR+ouOEZt4RGqmLQnQfdZyZTAOBS95izXjEchWM+W0EEXh+wfvCAphcyfSK0m+KZRsc6z5CN9FvF3nkfVN+pt7G59j0+CbJpGHDqXQm9OcuUqqbHkvUNArrNtHpZSadK6m2TewBuLeujLqG1MQD7P7jzDMvg8vMjOiX7KaqQoKaMlWdM6woEusNT0ddua4ReJ+PJtk7W3AjNIVYGhO47cGjFrb7i4gHPQ==', + iv: '640c59919fa84f7aa6ea9dcaba8c7a0b' + } + }; + } + }; + }); + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + // endpoint: '', + apiPath: '/api/blockChain/chainStatus', + methodName: 'getChainStatus', + arguments: [], + timestamp: 1718794867 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + const data = { + action: 'api', + params: { + encryptedParams: + 'bJiUPLoclvINMQDHJw2Dt4sGCFB5LV65UaIwUuoqs+O+K3H7bhexPNUecHrbqiSxfpoynsh72lrAWcykNPsfL8/XxYqnmJnGACdA+vCRVIdmL91/FzXfZoE8LgfhdELooVk2PCsfVLUuiOfZPVqo05SPvU320hIJbm4jqyCDTnNofLvSwq+bFULdpa1QmLxMiLaiKXXj6ktO3nER75sb3hLk7Lrun3rRvnFrbfC2Qlw4Zia2b0yMIacC0DmomWU0', + iv: '2ef43a6a71ed4aad9811ffba7d39464f' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '45b66a0738ff48a7b06cec79ff673dba' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invoke action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith( + 'Sending contract ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx method Transfer...' + ); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJjb250cmFjdEFkZHJlc3MlMjIlM0ElMjJBU2gyV3Q3blNFbVlxbkd4UFB6cDRwblZEVTR1aGoxWFc5U2U1VmVaY1gyVURkeWp4JTIyJTJDJTIyY29udHJhY3RNZXRob2QlMjIlM0ElMjJUcmFuc2ZlciUyMiUyQyUyMmFyZ3VtZW50cyUyMiUzQSU1QiU3QiUyMm5hbWUlMjIlM0ElMjJwYXJhbXMlMjIlMkMlMjJ2YWx1ZSUyMiUzQSU3QiUyMnN5bWJvbCUyMiUzQSUyMkVMRiUyMiUyQyUyMmFtb3VudCUyMiUzQTEwMDAwMDAwMCUyQyUyMm1lbW8lMjIlM0ElMjJ5ZWFoJTIyJTJDJTIydG8lMjIlM0ElMjIyUkNMbVpRMjI5MXhEd1NiREVKUjZuTGhGSmNNa3lmclZUcTFpMVl4V0M0U2RZNDlhNiUyMiU3RCU3RCU1RCUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NTI5NzIlN0Q=', + encrypt: () => { + return { + id: 'f84f494d82ce42c5b4018d37d6bd1d34', + result: { + encryptedResult: + 'gE0Mdb25HVglTZdjs26pvgq337LybbfPFZOsL/FWeKi/EBFdT+Vv8J+8xnU5dp/NNy49gTJQI8ggm9QRvpLz2rSb0fT+9fakQcC5REpOIO+WzZ93OCLE2pg/dRyTpyaeZNc7Hk9TGYV5puG2BfLye9ZpdxcUvMzaNqlw6s6frZp2m5djHRskGi+zcsU95JbjEDeqh1s2xtOBbL7JvrT/QTccSPcgTFFLYfoYGhD+SMt/+o049lBGa8c91rQRPbFj', + iv: '9e5d79cb079d49a295e5fa5f795a06c4' + } + }; + } + }; + }); + + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + endpoint: '', + contractAddress: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx', + contractMethod: 'Transfer', + arguments: [ + { + name: 'params', + value: { + symbol: 'ELF', + amount: 1 * 10 ** 8, + memo: 'yeah', + to: '2RCLmZQ2291xDwSbDEJR6nLhFJcMkyfrVTq1i1YxWC4SdY49a6' + } + } + ], + timestamp: 1718852972 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'invoke', + params: { + encryptedParams: + 'CWI3wEtHiyihMfMerb4UmU5wAOnv2Z652Hho7CltDuKwr8ZD69FNvjeoaxwg14bwxvSHRe5Myjuzp06wGrmRv1i+FaZfNrhBYu3afPqJx4cE6a6HSXwAOoUz3yhHTFU+gTSTeOS3MjkZUcEquu6/RYb0k4g6IwTWgxyRe9hppnbYJO3f0YGqQiP1M2awfWNKgs/OzpqI9FPqLRH/5Rzb1IKNHx8utks02vA2dVna+HgF9H13Pnu7joyuoz/LlKhZJwrAow4Me1c93+KIY1dgVjwgVxyIqxQ9vFe2dBxlPCJWvQB4/NZXVwFKeOlBAEy7o0tpUKnYX5NY7may6fnv1avw2Doi7xnCa4tLrZT4FGzpewxTCvg2WARjEqm/XqQQkXOPTv/wuCUmh6LEkMcccFgR7O87SPLkhLCv5QCrlWwBa4DkxgYO0Uo32X1igdTMwXLvu4yiPT7Od4pfyvMRuMwVEFmqbblbLpT/U1WwzIIcTG3x0CFiw0lJCdZ2zIjy5vIGp/ybxLez3TNn39e8CJTOU95XXsg6xcj2szF4LpE=', + iv: '2fccb196db614d05abc09deaa3798242' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'f84f494d82ce42c5b4018d37d6bd1d34' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invokeRead action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith( + 'Calling contract 2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2 method GetContractAddressByName...' + ); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJjb250cmFjdEFkZHJlc3MlMjIlM0ElMjIyVUtRbkhjUXZoQlQ2WDZVTHRmbnVoM2I5UFZSdlZNRXJvSEhrY0s0WWZjb0gxWjF4MiUyMiUyQyUyMmNvbnRyYWN0TWV0aG9kJTIyJTNBJTIyR2V0Q29udHJhY3RBZGRyZXNzQnlOYW1lJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTdCJTIybmFtZSUyMiUzQSUyMnBhcmFtcyUyMiUyQyUyMnZhbHVlJTIyJTNBJTIyYTJhMDBmODU4M2MwOGRhYTAwYjgwYjBiYmFjNDY4NDM5NmZlOTY2YjY4M2VhOTU2YTYzYmQ4ODQ1ZWVlNmFlNyUyMiU3RCU1RCUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NDkwMzElN0Q=', + encrypt: () => { + return { + id: '0dfbd2c9112140f485e6d809b3a70744', + result: { + encryptedResult: + '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', + iv: '883c926ef17a4c97845c6cad65a030e9' + } + }; + } + }; + }); + + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + // endpoint: '', + contractAddress: '2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2', + contractMethod: 'GetContractAddressByName', + arguments: [ + { + name: 'params', + value: 'a2a00f8583c08daa00b80b0bbac4684396fe966b683ea956a63bd8845eee6ae7' + } + ], + timestamp: 1718849031 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'invokeRead', + params: { + encryptedParams: + 'lqmIluuPMyQw/2/LXMEnKYfxp5KsQvwPpAEPCvsUc6Cf0X1K7d1CcCKv+h6W35vV6+d3sOSZLyeX0SIM7375wKsu14A+tO1OiaJ9DdKyJ6LJo+Mj+aA2Qp0N8zaXQmoTZPsKg/VYI3+YFXQU0tkPUCb+/YkPs/aE5T2KItU9WI3hVN6Xkd0vK2dShaTwhngVeXlcXeymcH/C0CevesDY+opYOHfqY/jP0LqDeXjXUP06LKGx+uMObJJvupslRI2HTbfwJWNYbsM3LY6VCbnA4tKnsfdxZZ3PvIoD6LRaMoIGjbsaTKl0hyZ38TPh+15r+OcoJjWka0TYpTFk93pTKnyGnkClGikWu4VNNe9CKKTSksjc5SNsBNNimxd/L4LLw2/yPIG3ZsaQ0AliaWOhO9SuvPKZnWBPdX/I4sPLkX1UZ5p0adUzQfXjnom2vCg/LeN8TcThw/+LlGAct0c4uQ==', + iv: 'da4512e2c1ed4f819d86fb3816b55aca' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '98c8b690fbbe43ca9f6fd1f72c18f2db' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle getContractMethods action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhZGRyZXNzJTIyJTNBJTIyQVNoMld0N25TRW1ZcW5HeFBQenA0cG5WRFU0dWhqMVhXOVNlNVZlWmNYMlVEZHlqeCUyMiUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NTI5NjMlN0Q=', + encrypt: () => { + return { + id: '7c4aafc53b804bb189c77ea95baf70c1', + result: { + encryptedResult: + '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', + iv: '9038ab6200ac48c0a0ffef278f5afbb8' + } + }; + } + }; + }); + + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + endpoint: '', + address: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx', + timestamp: 1718852963 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'getContractMethods', + params: { + encryptedParams: + 'bY89mIBq4wRbI0LWkDN52aFFgbgJbusedG+A54+sOTOnlSk9PxQ93Nw92D8Hc2zBL9DDROudC75dlqxCP8nGD3NKZyeAPQXM1BFTpc/8+OoD8AVpp+XAkO2SFFjHzS35JqspnaeJuJHKGvvnySFUkR/0OOq6ES8pj0nsogPl8wupWHlcDp0+mUo2hrYPddBZ', + iv: 'ea22631f45b34416ae01d95f095cba49' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '7c4aafc53b804bb189c77ea95baf70c1' + }; + clientSocket.emit('bridge', data); + }, 40000); + + test('should handle disconnect action', done => { + clientSocket.on('bridge', message => { + expect(logger.info).toHaveBeenCalledWith('Message received'); + expect(logger.info).toHaveBeenCalledWith('App 8a430b69-31df-5ea9-8a55-695faf0623d9 disconnected'); + expect(logger.error).not.toHaveBeenCalled(); + done(); + }); + + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyY29kZSUyMiUzQTAlMkMlMjJtc2clMjIlM0ElMjJzdWNjZXNzJTIyJTJDJTIyZXJyb3IlMjIlM0ElNUIlNUQlMkMlMjJkYXRhJTIyJTNBJTdCJTdEJTdE', + encrypt: () => { + return { + id: 'ded9643a0095440a9a5684c4b4c10e35', + result: { + encryptedResult: + 'BytgmULgGg7yEJ9EOvqipB+8uwBjAD4Dv44X7nba6j+ISTnx4QCFtjNpCAbDw9DloC/FMfl/4q+QbORXbtE1DqbGsae0bsAUQ03jLFU40Aui9Dq/AZLrcJ7/usU+2pbk', + iv: 'b08cbd888f8a400e8c0ded10826f4c03' + } + }; + } + }; + }); + + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + endpoint: '', + address: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx', + timestamp: 1718852963 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'disconnect', + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + test('should handle null action', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('You should set a action name')); + done(); + }); + + const data = { + action: null, + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + test('should handle undefined action', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('You should set a action name')); + done(); + }); + + const data = { + action: undefined, + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + test('should handle empty string action', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('You should set a action name')); + done(); + }); + + const data = { + action: '', + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle not supported action', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('test is not supported')); + done(); + }); + + const data = { + action: 'test', + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle not connected appId', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error('AppId test-id has not connected')); + done(); + }); + + const data = { + action: 'account', + params: { + encryptedParams: 'K/ZzGwmGdGBvypMkYEFkKNj8BiGnqndWwPPar1vejw/FOFptyhGTBe27NCRObzoR', + iv: 'f009bd251db14d13b1e87273b8b828b5' + }, + appId: 'test-id', + id: 'ded9643a0095440a9a5684c4b4c10e35' + }; + clientSocket.emit('bridge', data); + }); + test('should handle Timestamp is not valid', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith(new Error('Timestamp is not valid')); + done(); + }); + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ timestamp: 1718784774 }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(false); + + const data = { + action: 'account', + params: { + encryptedParams: 'yi4N41XoCTSz0ibXKQ2/rIYZz8D0u6vbaRxLPx+cu7AQjv3nsdWA9bqDSeQMr0ye', + iv: '67b83b89bdd44e2f8bcbb02251783825' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '281537101483488da8c12a9b02c4f563' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle not supported api', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error(`Not support api /test`)); + done(); + }); + + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJhcGlQYXRoJTIyJTNBJTIyJTJGYXBpJTJGYmxvY2tDaGFpbiUyRmNoYWluU3RhdHVzJTIyJTJDJTIybWV0aG9kTmFtZSUyMiUzQSUyMmdldENoYWluU3RhdHVzJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTVEJTJDJTIydGltZXN0YW1wJTIyJTNBMTcxODc5NDg2NyU3RA====', + encrypt: () => { + return { + id: '45b66a0738ff48a7b06cec79ff673dba', + result: { + encryptedResult: + 'WOsz8XNcMS66yp6qni9+oV9m1pelGe7uLWuICVrc4Urq1gH+XmErWMKZy7vCqPYHfS3kBVwnKMl5WHS6rEUQBZg4mPux3IfozDgNrY7NC9DN81kt/h5tKbSHSx6GMgB9GeVLH5ELj+O+ZZXSszge5k2/xRa0GZYGdX04EXG5OO4XJ+3KxE5YXmO1PhfQenZR19GynQQxymMwByRxZM0GvpKhHM3oQbq7s600wPvulHdIdXY3W4QCPyLTqQD+18aNEVzFYZjr0WGZJKVTv8NsS04gDghVIShmMCTzkvEYPM0kI9JocfSDSI5MqsMGbdpNhKAYCFBgC3ILOpbpGXG53qUUdhFcDIAnj6yeyB8ZG+pkMEBcbn+aZoWjdqS/z7UrIF0RatCKGRzZyOO8La0d+KsHOm11d/cukTsyyz9/Yvds1Equlyk9vz4BUQNkIaM2zXP6AZfE3QwoMf0IhcXdFCEoBybDNEuaxueEBVkHdM+9WF4EwqX2iRHgXa4D6qKfIGgD5zhTASWCEOJj5Q57/eSu2cZ6xT+hJiX95sO1PB0TqatQMhGz2n/fAKf5roKAXxgjAx/eiAzygTVldmMGHQtj++UveilXCeZ9BEInb3K3mRyx5pc/t874dGF44l/VqC18RK0X1xgUDYcV2ru/HJwH0OHKxDJSDlEIzME8U5x6FVGHc33kFgY1LcLEXHd+r0MpSYdtbHiwwPPTSGhgFwnpF+mWZnQepQh4VsNcfJRg8tMG9Jcx4vM9vaT7b7a9LaT333WZ1d6DiXQYZ3MJBewUwqfNlqTaz+O7o51tOXMHzX4FxcUwgsGLSgH9al5hjWb3uZZarXrF/dhB4zyQBmTdPO6k4cowcoWLvEnKiSU2mYKqSvUoqmM1o7cI21VD4reV7XQiFxUEP3CNlkNFm1imImhANZoltx+GkzZRsL9IHMgkEO5ixLR+ouOEZt4RGqmLQnQfdZyZTAOBS95izXjEchWM+W0EEXh+wfvCAphcyfSK0m+KZRsc6z5CN9FvF3nkfVN+pt7G59j0+CbJpGHDqXQm9OcuUqqbHkvUNArrNtHpZSadK6m2TewBuLeujLqG1MQD7P7jzDMvg8vMjOiX7KaqQoKaMlWdM6woEusNT0ddua4ReJ+PJtk7W3AjNIVYGhO47cGjFrb7i4gHPQ==', + iv: '640c59919fa84f7aa6ea9dcaba8c7a0b' + } + }; + } + }; + }); + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + endpoint: '', + apiPath: '/test', + methodName: 'getChainStatus', + arguments: [], + timestamp: 1718794867 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'api', + params: { + encryptedParams: + 'bJiUPLoclvINMQDHJw2Dt4sGCFB5LV65UaIwUuoqs+O+K3H7bhexPNUecHrbqiSxfpoynsh72lrAWcykNPsfL8/XxYqnmJnGACdA+vCRVIdmL91/FzXfZoE8LgfhdELooVk2PCsfVLUuiOfZPVqo05SPvU320hIJbm4jqyCDTnNofLvSwq+bFULdpa1QmLxMiLaiKXXj6ktO3nER75sb3hLk7Lrun3rRvnFrbfC2Qlw4Zia2b0yMIacC0DmomWU0', + iv: '2ef43a6a71ed4aad9811ffba7d39464f' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '45b66a0738ff48a7b06cec79ff673dba' + }; + clientSocket.emit('bridge', data); + }); + + test('should handle invoke with not supported method', done => { + clientSocket.on('bridge', message => { + expect(logger.error).toHaveBeenCalledWith('error happened'); + expect(logger.error).toHaveBeenCalledWith(new Error(`No such method Test`)); + done(); + }); + + Encrypt.mockImplementation(() => { + return { + decrypt: () => + 'JTdCJTIyZW5kcG9pbnQlMjIlM0ElMjIlMjIlMkMlMjJjb250cmFjdEFkZHJlc3MlMjIlM0ElMjIyVUtRbkhjUXZoQlQ2WDZVTHRmbnVoM2I5UFZSdlZNRXJvSEhrY0s0WWZjb0gxWjF4MiUyMiUyQyUyMmNvbnRyYWN0TWV0aG9kJTIyJTNBJTIyR2V0Q29udHJhY3RBZGRyZXNzQnlOYW1lJTIyJTJDJTIyYXJndW1lbnRzJTIyJTNBJTVCJTdCJTIybmFtZSUyMiUzQSUyMnBhcmFtcyUyMiUyQyUyMnZhbHVlJTIyJTNBJTIyYTJhMDBmODU4M2MwOGRhYTAwYjgwYjBiYmFjNDY4NDM5NmZlOTY2YjY4M2VhOTU2YTYzYmQ4ODQ1ZWVlNmFlNyUyMiU3RCU1RCUyQyUyMnRpbWVzdGFtcCUyMiUzQTE3MTg4NDkwMzElN0Q=', + encrypt: () => { + return { + id: '0dfbd2c9112140f485e6d809b3a70744', + result: { + encryptedResult: + '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', + iv: '883c926ef17a4c97845c6cad65a030e9' + } + }; + } + }; + }); + + jest.spyOn(utils, 'deserializeMessage').mockReturnValue({ + endpoint: '', + contractAddress: '2UKQnHcQvhBT6X6ULtfnuh3b9PVRvVMEroHHkcK4YfcoH1Z1x2', + contractMethod: 'Test', + arguments: [ + { + name: 'params', + value: 'a2a00f8583c08daa00b80b0bbac4684396fe966b683ea956a63bd8845eee6ae7' + } + ], + timestamp: 1718849031 + }); + jest.spyOn(utils, 'checkTimestamp').mockReturnValue(true); + + const data = { + action: 'invokeRead', + params: { + encryptedParams: + 'lqmIluuPMyQw/2/LXMEnKYfxp5KsQvwPpAEPCvsUc6Cf0X1K7d1CcCKv+h6W35vV6+d3sOSZLyeX0SIM7375wKsu14A+tO1OiaJ9DdKyJ6LJo+Mj+aA2Qp0N8zaXQmoTZPsKg/VYI3+YFXQU0tkPUCb+/YkPs/aE5T2KItU9WI3hVN6Xkd0vK2dShaTwhngVeXlcXeymcH/C0CevesDY+opYOHfqY/jP0LqDeXjXUP06LKGx+uMObJJvupslRI2HTbfwJWNYbsM3LY6VCbnA4tKnsfdxZZ3PvIoD6LRaMoIGjbsaTKl0hyZ38TPh+15r+OcoJjWka0TYpTFk93pTKnyGnkClGikWu4VNNe9CKKTSksjc5SNsBNNimxd/L4LLw2/yPIG3ZsaQ0AliaWOhO9SuvPKZnWBPdX/I4sPLkX1UZ5p0adUzQfXjnom2vCg/LeN8TcThw/+LlGAct0c4uQ==', + iv: 'da4512e2c1ed4f819d86fb3816b55aca' + }, + appId: '8a430b69-31df-5ea9-8a55-695faf0623d9', + id: '98c8b690fbbe43ca9f6fd1f72c18f2db' + }; + clientSocket.emit('bridge', data); + }); +}); diff --git a/test/command/dappServer/utils.test.js b/test/command/dappServer/utils.test.js new file mode 100644 index 0000000..ab6d95f --- /dev/null +++ b/test/command/dappServer/utils.test.js @@ -0,0 +1,76 @@ +import { serializeMessage, deserializeMessage, checkTimestamp } from '../../../src/command/dappServer/utils'; + +describe('Utils module', () => { + describe('serializeMessage', () => { + test('should serialize a valid JSON object', () => { + const data = { key: 'value' }; + const serialized = serializeMessage(data); + const expected = Buffer.from(encodeURIComponent(JSON.stringify(data))).toString('base64'); + expect(serialized).toBe(expected); + }); + + test('should return an empty string for null input', () => { + const serialized = serializeMessage(null); + expect(serialized).toBe(''); + }); + + test('should return an empty string for undefined input', () => { + const serialized = serializeMessage(undefined); + expect(serialized).toBe(''); + }); + + test('should serialize an empty object', () => { + const data = {}; + const serialized = serializeMessage(data); + const expected = Buffer.from(encodeURIComponent(JSON.stringify(data))).toString('base64'); + expect(serialized).toBe(expected); + }); + }); + + describe('deserializeMessage', () => { + test('should deserialize a valid base64-encoded string', () => { + const data = { key: 'value' }; + const serialized = Buffer.from(encodeURIComponent(JSON.stringify(data))).toString('base64'); + const deserialized = deserializeMessage(serialized); + expect(deserialized).toEqual(data); + }); + + test('should return an empty object for an invalid JSON string', () => { + const invalidSerialized = Buffer.from('invalid json').toString('base64'); + const deserialized = deserializeMessage(invalidSerialized); + expect(deserialized).toBe('invalid json'); + }); + + test('should return an empty string for an empty input string', () => { + const deserialized = deserializeMessage(''); + expect(deserialized).toBe(''); + }); + }); + + describe('checkTimestamp', () => { + test('should return true for a valid timestamp within the buffer', () => { + const time = Math.ceil(new Date().getTime() / 1000); + const isValid = checkTimestamp(time); + expect(isValid).toBe(true); + }); + + test('should return false for a timestamp outside the buffer', () => { + // 5 minutes ago + const time = Math.ceil(new Date().getTime() / 1000) - 5 * 60; + const isValid = checkTimestamp(time, 4 * 60); + expect(isValid).toBe(false); + }); + + test('should return false for an invalid timestamp', () => { + const isValid = checkTimestamp('invalid timestamp'); + expect(isValid).toBe(false); + }); + + test('should return false for a future timestamp', () => { + // 1 minute in the future + const time = Math.ceil(new Date().getTime() / 1000) + 60; + const isValid = checkTimestamp(time); + expect(isValid).toBe(false); + }); + }); +}); diff --git a/test/command/deploy.test.js b/test/command/deploy.test.js index 5e98787..3d18ba6 100644 --- a/test/command/deploy.test.js +++ b/test/command/deploy.test.js @@ -4,6 +4,7 @@ import path from 'path'; import chalk from 'chalk'; import DeployCommand from '../../src/command/deploy.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('chalk', () => { return { @@ -21,10 +22,6 @@ describe('DeployCommand', () => { let deployCommand; let consoleSpy; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); deployCommand = new DeployCommand(sampleRc); @@ -47,5 +44,5 @@ describe('DeployCommand', () => { commander.parse([process.argv[0], '', 'console', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await deployCommand.run(commander); expect(consoleSpy).toHaveBeenCalledWith(expectedTips); - }, 20000); + }); }); diff --git a/test/command/event.test.js b/test/command/event.test.js index d0a4a37..f8ae259 100644 --- a/test/command/event.test.js +++ b/test/command/event.test.js @@ -3,6 +3,7 @@ import path from 'path'; import EventCommand from '../../src/command/event.js'; import { logger, plainLogger } from '../../src/utils/myLogger'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -10,10 +11,6 @@ describe('EventCommand', () => { let eventCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -64,7 +61,7 @@ describe('EventCommand', () => { `\nThe results returned by \nTransaction: ${txId} is: \n${JSON.stringify(logs, null, 2)}` ); expect(oraInstanceMock.fail).not.toHaveBeenCalled(); - }, 20000); + }); test('should log "not mined" if transaction status is not mined', async () => { const txId = '3553df418c6ec9a159560440f13a6ae29f786392574737036cf63786321c8a40'; @@ -80,7 +77,7 @@ describe('EventCommand', () => { await eventCommand.run(commander, txId); expect(plainLogger.info).toHaveBeenCalledWith(`Transaction ${txId} is not mined`); expect(oraInstanceMock.fail).not.toHaveBeenCalled(); - }, 20000); + }); test('should log error and fail on exception', async () => { const txId = 'test'; @@ -95,5 +92,5 @@ describe('EventCommand', () => { commander.parse([process.argv[0], '', 'event', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await eventCommand.run(commander, txId); expect(oraInstanceMock.fail).toHaveBeenCalledWith('Failed!'); - }, 20000); + }); }); diff --git a/test/command/getBlkHeight.test.js b/test/command/getBlkHeight.test.js index 2f922f7..270a48e 100644 --- a/test/command/getBlkHeight.test.js +++ b/test/command/getBlkHeight.test.js @@ -2,6 +2,7 @@ import { Command } from 'commander'; import path from 'path'; import GetBlkHeightCommand from '../../src/command/getBlkHeight.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -9,10 +10,6 @@ describe('GetBlkHeightCommand', () => { let getBlkHeightCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -41,7 +38,7 @@ describe('GetBlkHeightCommand', () => { commander.parse([process.argv[0], '', 'get-blk-height', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await getBlkHeightCommand.run(commander); expect(oraInstanceMock.succeed).toHaveBeenCalled(); - }, 20000); + }); test('should log error and fail on exception', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); diff --git a/test/command/getBlkInfo.test.js b/test/command/getBlkInfo.test.js index 4b0bc78..53ed52f 100644 --- a/test/command/getBlkInfo.test.js +++ b/test/command/getBlkInfo.test.js @@ -4,6 +4,7 @@ import path from 'path'; import GetBlkInfoCommand from '../../src/command/getBlkInfo.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -11,10 +12,6 @@ describe('GetBlkInfoCommand', () => { let getBlkInfoCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -69,7 +66,7 @@ describe('GetBlkInfoCommand', () => { Time: '2022-06-02T11:28:40.5094851Z' } }); - }, 20000); + }); test('should get block info by hash and succeed', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -108,7 +105,7 @@ describe('GetBlkInfoCommand', () => { Time: '2022-06-02T11:28:40.5094851Z' } }); - }, 20000); + }); test('should log error and fail on exception', async () => { jest.spyOn(process, 'exit').mockImplementation(() => {}); const commander = new Command(); diff --git a/test/command/getChainStatus.test.js b/test/command/getChainStatus.test.js index d5bdf53..7347c86 100644 --- a/test/command/getChainStatus.test.js +++ b/test/command/getChainStatus.test.js @@ -2,6 +2,7 @@ import { Command } from 'commander'; import path from 'path'; import GetChainStatusCommand from '../../src/command/getChainStatus.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -9,10 +10,6 @@ describe('GetChainStatusCommand', () => { let getChainStatusCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -41,7 +38,7 @@ describe('GetChainStatusCommand', () => { commander.parse([process.argv[0], '', 'get-chain-status', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await getChainStatusCommand.run(commander); expect(oraInstanceMock.succeed).toHaveBeenCalled(); - }, 20000); + }); test('should log error and fail on exception', async () => { jest.spyOn(process, 'exit').mockImplementation(() => {}); const commander = new Command(); @@ -58,5 +55,5 @@ describe('GetChainStatusCommand', () => { }); await getChainStatusCommand.run(commander); expect(oraInstanceMock.fail).toHaveBeenCalled(); - }, 20000); + }); }); diff --git a/test/command/getTxResult.test.js b/test/command/getTxResult.test.js index c8b2fc4..fd5d8a3 100644 --- a/test/command/getTxResult.test.js +++ b/test/command/getTxResult.test.js @@ -2,6 +2,7 @@ import { Command } from 'commander'; import path from 'path'; import GetTxResultCommand from '../../src/command/getTxResult.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -9,10 +10,6 @@ describe('GetTxResultCommand', () => { let getTxResultCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -42,7 +39,7 @@ describe('GetTxResultCommand', () => { commander.parse([process.argv[0], '', 'get-tx-result', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await getTxResultCommand.run(commander, txId); expect(oraInstanceMock.succeed).toHaveBeenCalled(); - }, 20000); + }); test('should log error and fail on validation error', async () => { jest.spyOn(process, 'exit').mockImplementation(() => {}); const commander = new Command(); @@ -57,5 +54,5 @@ describe('GetTxResultCommand', () => { await getTxResultCommand.run(commander, true); expect(process.exit).toHaveBeenCalledWith(1); expect(oraInstanceMock.fail).toHaveBeenCalled(); - }, 20000); + }); }); diff --git a/test/command/load.test.js b/test/command/load.test.js index fc1a69e..95ef3bd 100644 --- a/test/command/load.test.js +++ b/test/command/load.test.js @@ -5,6 +5,7 @@ import LoadCommand from '../../src/command/load.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger'; import { saveKeyStore } from '../../src/utils/wallet'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/wallet'); jest.mock('../../src/utils/myLogger'); @@ -13,10 +14,6 @@ describe('LoadCommand', () => { let loadCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); const privateKey = '9a2c6023e8b2221f4b02f4ccc5128392c1bd968ae45a42fa62848d793fff148f'; beforeEach(() => { oraInstanceMock = { @@ -55,7 +52,7 @@ describe('LoadCommand', () => { ); expect(logger.info).toHaveBeenCalledWith('Address : GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'); expect(saveKeyStore).toHaveBeenCalled(); - }, 20000); + }); test('should load wallet from Mnemonic and succeed', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -79,7 +76,7 @@ describe('LoadCommand', () => { 'Public Key : 04449094b89d0445c920434ea09d87ba8d9bf95d8a3971ee03572a1f666ef2241cc3ada03d47736c005d28bbef8468042e77a084ea11b8aca395ac7686335f4712' ); expect(logger.info).toHaveBeenCalledWith('Address : SbWhnq3XU8yeiUTYJmZBSgt7ekgszRXHxh8qNqkFj9g6d3bWh'); - }, 20000); + }); test('should load wallet from privateKey and succeed without saving to file', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -100,7 +97,7 @@ describe('LoadCommand', () => { ); expect(logger.info).toHaveBeenCalledWith('Address : GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'); expect(oraInstanceMock.succeed).toHaveBeenCalledWith('Succeed!'); - }, 20000); + }); test('should log error and fail on validation error', async () => { const commander = new Command(); @@ -117,7 +114,7 @@ describe('LoadCommand', () => { }); await loadCommand.run(commander, privateKey, false, true); expect(oraInstanceMock.fail).toHaveBeenCalled(); - }, 20000); + }); test('should fail when trying to use old version SDK', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -130,5 +127,5 @@ describe('LoadCommand', () => { commander.parse([process.argv[0], '', 'load', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir]); await loadCommand.run(commander, 'xxx xxx', true, false); expect(oraInstanceMock.fail).toHaveBeenCalledWith('Please install older versions of aelf-command before v1.0.0!'); - }, 20000); + }); }); diff --git a/test/command/proposal.test.js b/test/command/proposal.test.js index 882dc15..e1bf002 100644 --- a/test/command/proposal.test.js +++ b/test/command/proposal.test.js @@ -10,6 +10,7 @@ import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger'; import * as utils from '../../src/utils/utils.js'; import { getWallet } from '../../src/utils/wallet.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); jest.mock('inquirer'); @@ -27,10 +28,6 @@ describe('ProposalCommand processAddressAfterPrompt', () => { let proposalCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { start: jest.fn(), @@ -53,16 +50,12 @@ describe('ProposalCommand processAddressAfterPrompt', () => { }; const result = await proposalCommand.processAddressAfterPrompt(aelf, wallet, answerInput); expect(result.address).toBe(contractAddress); - }, 20000); + }); }); describe('ProposalCommand run', () => { let proposalCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); let mockParliamentContract, mockGenesisContract; beforeEach(() => { oraInstanceMock = { @@ -153,7 +146,7 @@ describe('ProposalCommand run', () => { expect(oraInstanceMock.succeed).toHaveBeenCalled(); expect(logger.info).toHaveBeenCalledWith({ TransactionId: 'mockTxId' }); expect(logger.info).toHaveBeenCalledWith('Proposal id: mockProposal.'); - }, 20000); + }); test('should run and show pending info', async () => { const commander = new Command(); commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000'); @@ -204,7 +197,7 @@ describe('ProposalCommand run', () => { expect(logger.info).toHaveBeenCalledWith( 'Transaction is still pending, you can get proposal id later by running yellow(aelf-command event mockTxId)' ); - }, 20000); + }); test('should handle failure to create proposal', async () => { const commander = new Command(); diff --git a/test/command/wallet.test.js b/test/command/wallet.test.js index 3428b8b..9b32c43 100644 --- a/test/command/wallet.test.js +++ b/test/command/wallet.test.js @@ -5,6 +5,7 @@ import GetTxResultCommand from '../../src/command/wallet.js'; import { userHomeDir } from '../../src/utils/userHomeDir.js'; import { logger } from '../../src/utils/myLogger.js'; import { getWallet } from '../../src/utils/wallet.js'; +import { endpoint as endPoint, account, password, dataDir } from '../constants.js'; jest.mock('../../src/utils/myLogger'); @@ -12,10 +13,6 @@ describe('WalletCommand', () => { let walletCommand; let oraInstanceMock; const sampleRc = { getConfigs: jest.fn() }; - const endPoint = 'https://tdvw-test-node.aelf.io/'; - const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; - const password = '1234*Qwer'; - const dataDir = path.resolve(__dirname, '../datadir/aelf'); beforeEach(() => { oraInstanceMock = { @@ -54,7 +51,7 @@ describe('WalletCommand', () => { 'Public Key : 04703bbe95e986c9d901f28edd60975a7a6c3b2dce41dfec2e7983d293c600e8249642a3da379c4194a6d62bd89afe6753e81acfc2b6bbf3b40736ee0949102071' ); expect(logger.info).toHaveBeenCalledWith('Address : GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'); - }, 20000); + }); test('should handle errors and fail', async () => { jest.spyOn(require('../../src/utils/wallet'), 'getWallet').mockReturnValue(new Error('test error')); const commander = new Command(); @@ -69,5 +66,5 @@ describe('WalletCommand', () => { await walletCommand.run(commander); expect(oraInstanceMock.fail).toHaveBeenCalledWith('Failed!'); expect(logger.error).toHaveBeenCalled(); - }, 20000); + }); }); diff --git a/test/constants.js b/test/constants.js new file mode 100644 index 0000000..943dc4e --- /dev/null +++ b/test/constants.js @@ -0,0 +1,6 @@ +import path from 'path'; + +export const endpoint = 'https://tdvw-test-node.aelf.io/'; +export const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'; +export const password = '1234*Qwer'; +export const dataDir = path.resolve(__dirname, './dataDir/aelf'); diff --git a/test/dataDir/aelf/.aelfrc b/test/dataDir/aelf/.aelfrc new file mode 100644 index 0000000..0706a85 --- /dev/null +++ b/test/dataDir/aelf/.aelfrc @@ -0,0 +1,5 @@ +# THIS IS AN AUTOGENERATED FILE FOR AELF-COMMAND OPTIONS. DO NOT EDIT THIS FILE DIRECTLY. + + + +endpoint https://tdvw-test-node.aelf.io/ diff --git a/test/rc/index.test.js b/test/rc/index.test.js new file mode 100644 index 0000000..7fdc4be --- /dev/null +++ b/test/rc/index.test.js @@ -0,0 +1,79 @@ +import fs from 'fs'; +import path from 'path'; +import Registry from '../../src/rc/index'; +import { userHomeDir } from '../../src/utils/userHomeDir'; +import { endpoint, account, password, dataDir } from '../constants.js'; + +jest.mock('../../src/utils/userHomeDir', () => { + const path = require('path'); + return { + userHomeDir: path.resolve(__dirname) + }; +}); + +describe('Registry', () => { + afterEach(() => { + jest.clearAllMocks(); + delete process.env.AELF_CLI_ENDPOINT; + }); + afterAll(() => { + fs.unlinkSync(path.resolve(userHomeDir, 'aelf/.aelfrc')); + fs.rmdirSync(path.resolve(userHomeDir, 'aelf')); + }); + test('should get file or not', () => { + const result = Registry.getFileOrNot(path.resolve(__dirname, '../dataDir/aelf/.aelfrc')); + expect(result).toBe(`# THIS IS AN AUTOGENERATED FILE FOR AELF-COMMAND OPTIONS. DO NOT EDIT THIS FILE DIRECTLY. + + + +endpoint https://tdvw-test-node.aelf.io/ +`); + }); + test('should load config', () => { + const result = Registry.loadConfig(); + expect(result).toEqual({}); + }); + test('should get config from env', () => { + // mock + process.env.AELF_CLI_ENDPOINT = 'http://localhost:1234'; + const result = Registry.getConfigFromEnv(); + expect(result).toEqual({ endpoint: 'http://localhost:1234' }); + }); + test('should stringify', () => { + const result = Registry.stringify(); + expect(result).toEqual([ + '# THIS IS AN AUTOGENERATED FILE FOR AELF-COMMAND OPTIONS. DO NOT EDIT THIS FILE DIRECTLY.', + '', + '', + '' + ]); + }); + test('should get and set options correctly', () => { + const registry = new Registry(); + registry.setOption('endpoint', endpoint); + expect(registry.getOption('endpoint')).toBe(endpoint); + }); + test('should save options to file', () => { + const registry = new Registry(); + registry.saveOption('endpoint', endpoint); + expect(fs.readFileSync(registry.globalConfigLoc).toString()).toContain(`endpoint ${endpoint}`); + }); + test('should delete config key from file', () => { + const registry = new Registry(); + registry.saveOption('endpoint', endpoint); + registry.deleteConfig('endpoint'); + expect(fs.readFileSync(registry.globalConfigLoc).toString()).not.toContain(`endpoint ${endpoint}`); + }); + test('should get file configs correctly', () => { + const registry = new Registry(); + registry.saveOption('endpoint', endpoint); + const fileConfigs = registry.getFileConfigs(); + expect(fileConfigs.endpoint).toBe(endpoint); + }); + test('should get configs correctly', () => { + const registry = new Registry(); + registry.saveOption('endpoint', endpoint); + const configs = registry.getConfigs(); + expect(configs.endpoint).toBe(endpoint); + }); +}); diff --git a/test/utils/Logger.test.js b/test/utils/Logger.test.js new file mode 100644 index 0000000..c439d6c --- /dev/null +++ b/test/utils/Logger.test.js @@ -0,0 +1,52 @@ +import chalk from 'chalk'; +import Logger from '../../src/utils/Logger'; + +describe('Logger', () => { + let consoleLogSpy; + const fnName = 'trace', + level = 'Trace'; + beforeEach(() => { + consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleLogSpy.mockRestore(); + }); + + test(`should log correctly formatted message`, () => { + const logger = new Logger({ log: true, onlyWords: false, name: 'TestLogger' }); + const message = 'Test message'; + logger[fnName](message); + const expectedPrefix = `[${level}]: `; + const expectedLog = chalk.gray(`TestLogger ${expectedPrefix}${message}`); + // second params: add spaces + expect(consoleLogSpy).toHaveBeenCalledWith(expectedLog, ''); + }); + test(`should return correctly formatted chalk message`, () => { + const logger = new Logger({ log: false, onlyWords: false, name: 'TestLogger' }); + const message = 'Test message'; + const result = logger[fnName](message); + const expectedPrefix = `TestLogger [${level}]: `; + const expectedChalk = chalk(chalk.gray(`${expectedPrefix}${message}`)); + expect(result.trim()).toEqual(expectedChalk); + }); + test(`should log correctly formatted object message`, () => { + const logger = new Logger({ log: true, onlyWords: false, name: 'TestLogger' }); + const message = { key: 'value' }; + logger[fnName](message); + const expectedPrefix = `TestLogger [${level}]: \n`; + const expectedLog = chalk.gray(`${expectedPrefix}`); + expect(consoleLogSpy).toHaveBeenCalledWith(expectedLog, message); + }); + test(`should log correctly formatted object message (onlyWords: true)`, () => { + const logger = new Logger({ onlyWords: true, name: 'TestLogger' }); + logger.symbol = '*'; + const message = { key: 'value' }; + logger[fnName](message); + + const expectedPrefix = `* [${level}]: \n`; + const expectedLog = chalk.gray(`${expectedPrefix}`); + + expect(consoleLogSpy).toHaveBeenCalledWith(expectedLog, message); + }); +}); diff --git a/test/utils/fs.test.js b/test/utils/fs.test.js new file mode 100644 index 0000000..f9296cf --- /dev/null +++ b/test/utils/fs.test.js @@ -0,0 +1,50 @@ +import fs from 'fs'; +import os from 'os'; +import { writeFilePreservingEol } from '../../src/utils/fs.js'; +import { promisify } from '../../src/utils/utils'; + +jest.mock('fs'); +jest.mock('../../src/utils/utils', () => { + return { + promisify: fn => fn + }; +}); + +describe('File System Operators', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('writeFilePreservingEol', () => { + test('should write data with preserved EOL', async () => { + const path = '/path/to/existing/file.txt'; + const existingData = 'Line 1\nLine 2\nLine 3\n'; + const newData = 'New Line 1\nNew Line 2\nNew Line 3\n'; + const expectedData = 'New Line 1\nNew Line 2\nNew Line 3\n'; + + const mockBuffer = Buffer.from(existingData, 'utf-8'); + fs.existsSync.mockReturnValue(true); + fs.readFile.mockReturnValue(mockBuffer); + + let writtenData = ''; + fs.writeFile.mockImplementation((filePath, data) => { + writtenData = data.toString(); + }); + + await writeFilePreservingEol(path, newData); + expect(writtenData).toBe(expectedData); + }); + + test('should write data with default EOL if file does not exist', async () => { + const path = '/path/to/nonexistent/file.txt'; + const newData = 'Line 1\nLine 2\nLine 3\n'; + fs.existsSync.mockReturnValue(false); + let writtenData = ''; + fs.writeFile.mockImplementation((filePath, data) => { + writtenData = data.toString(); + }); + await writeFilePreservingEol(path, newData); + expect(writtenData).toBe(newData); + }); + }); +}); diff --git a/test/utils/userHomeDir.test.js b/test/utils/userHomeDir.test.js new file mode 100644 index 0000000..0fda4ac --- /dev/null +++ b/test/utils/userHomeDir.test.js @@ -0,0 +1,111 @@ +import path from 'path'; +import { homedir } from 'os'; +import { userHomeDir, home, isFakeRoot, isRootUser, ROOT_USER } from '../../src/utils/userHomeDir'; + +jest.mock('os', () => ({ + homedir: jest.fn(() => { + const mockHomeDir = '/mock/home'; + return mockHomeDir; + }) +})); +describe('userHomeDir', () => { + let originalPlatform; + let originalEnv; + const mockHomeDir = '/mock/home'; + beforeAll(() => { + originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform'); + originalEnv = { ...process.env }; + }); + + afterAll(() => { + Object.defineProperty(process, 'platform', originalPlatform); + process.env = originalEnv; + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + }); + + test('home should be the user home directory', () => { + expect(home).toBe(mockHomeDir); + }); + + test('isFakeRoot should return true if FAKEROOTKEY is set', () => { + process.env.FAKEROOTKEY = 'true'; + expect(isFakeRoot()).toBe(true); + }); + + test('isFakeRoot should return false if FAKEROOTKEY is not set', () => { + delete process.env.FAKEROOTKEY; + expect(isFakeRoot()).toBe(false); + }); + + test('isRootUser should return true if uid is 0', () => { + expect(isRootUser(0)).toBe(true); + }); + + test('isRootUser should return false if uid is not 0', () => { + expect(isRootUser(1000)).toBe(false); + }); + + test('isWindows should return true if platform is win32', () => { + Object.defineProperty(process, 'platform', { + value: 'win32' + }); + expect(process.platform).toBe('win32'); + }); + + test('isWindows should return false if platform is not win32', () => { + Object.defineProperty(process, 'platform', { + value: 'linux' + }); + expect(process.platform).toBe('linux'); + }); + + test('userHomeDir should be correct for Windows platform', () => { + Object.defineProperty(process, 'platform', { + value: 'win32' + }); + + jest.resetModules(); + const { userHomeDir } = require('../../src/utils/userHomeDir'); + expect(userHomeDir).toBe(path.resolve(mockHomeDir, './AppData/Local')); + }); + + test('userHomeDir should be correct for non-Windows platform', () => { + Object.defineProperty(process, 'platform', { + value: 'linux' + }); + + jest.resetModules(); + const { userHomeDir } = require('../../src/utils/userHomeDir'); + expect(userHomeDir).toBe(path.resolve(mockHomeDir, './.local/share')); + }); + + test('ROOT_USER should be true if user is root and not fake root', () => { + jest.spyOn(process, 'getuid').mockReturnValue(0); + delete process.env.FAKEROOTKEY; + + jest.resetModules(); + const { ROOT_USER } = require('../../src/utils/userHomeDir'); + expect(ROOT_USER).toBe(true); + }); + + test('ROOT_USER should be false if user is not root', () => { + jest.spyOn(process, 'getuid').mockReturnValue(1000); + + jest.resetModules(); + const { ROOT_USER } = require('../../src/utils/userHomeDir'); + expect(ROOT_USER).toBe(false); + }); + + test('ROOT_USER should be false if user is root but is fake root', () => { + jest.spyOn(process, 'getuid').mockReturnValue(0); + process.env.FAKEROOTKEY = 'true'; + + jest.resetModules(); + const { ROOT_USER } = require('../../src/utils/userHomeDir'); + expect(ROOT_USER).toBe(false); + }); +}); diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js new file mode 100644 index 0000000..d6301e4 --- /dev/null +++ b/test/utils/utils.test.js @@ -0,0 +1,394 @@ +import path from 'path'; +import inquirer from 'inquirer'; +import moment from 'moment'; +import { v4 as uuid } from 'uuid'; +import AElf from 'aelf-sdk'; +import { + promisify, + camelCase, + getContractMethods, + getContractInstance, + getMethod, + promptTolerateSeveralTimes, + isAElfContract, + getTxResult, + parseJSON, + randomId, + getParams, + deserializeLogs +} from '../../src/utils/utils'; +import { plainLogger } from '../../src/utils/myLogger'; +import { endpoint, account, password, dataDir } from '../constants.js'; + +jest.mock('inquirer'); + +describe('utils', () => { + afterEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + }); + + describe('promisify', () => { + test('should resolve with result when no error', async () => { + const mockFn = jest.fn((arg1, cb) => cb(null, arg1 + 1)); + const promiseFn = promisify(mockFn); + const result = await promiseFn(5); + expect(result).toBe(6); + }); + test('should reject with error when callback has error', async () => { + const mockFn = jest.fn((_, cb) => cb(new Error('Callback error'))); + const promiseFn = promisify(mockFn); + await expect(promiseFn(5)).rejects.toThrow('Callback error'); + }); + test('should handle firstData parameter correctly', async () => { + const mockFn = jest.fn((_, cb) => cb('result')); + const promiseFn = promisify(mockFn, true); + const result = await promiseFn(5); + expect(result).toBe('result'); + }); + }); + + describe('camelCase', () => { + test('should convert string to camelCase', () => { + expect(camelCase('hello_world')).toBe('helloWorld'); + }); + }); + + describe('isAElfContract', () => { + test('should return true for valid AElf contract name', () => { + expect(isAElfContract('aelf.contract')).toBe(true); + }); + + test('should return false for non-AElf contract name', () => { + expect(isAElfContract('not.aelf.contract')).toBe(false); + }); + }); + + describe('getContractMethods', () => { + test('should return methods starting with uppercase letters', () => { + const contract = { + Transfer: () => {}, + approve: () => {} + }; + const methods = getContractMethods(contract); + expect(methods).toEqual(['Transfer']); + }); + + test('should call plainLogger.fatal and exit process if no contract provided', () => { + const spyFatal = jest.spyOn(plainLogger, 'fatal').mockReturnValue(); + const spyExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); + getContractMethods(''); + expect(spyFatal).toHaveBeenCalled(); + expect(spyExit).toHaveBeenCalledWith(1); + }); + }); + + describe('getContractInstance', () => { + let oraInstance; + const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); + const contractAddress = '2cVQrFiXNaedBYmUrovmUV2jcF9Hf6AXbh12gWsD4P49NaX99y'; + const wallet = AElf.wallet.getWalletByPrivateKey('9a2c6023e8b2221f4b02f4ccc5128392c1bd968ae45a42fa62848d793fff148f'); + + beforeEach(() => { + oraInstance = { + start: jest.fn(), + succeed: jest.fn(), + fail: jest.fn() + }; + }); + + test('should fetch contract by address if not AElf contract', async () => { + const contractInstance = await getContractInstance(contractAddress, aelf, wallet, oraInstance); + expect(oraInstance.start).toHaveBeenCalledWith('Fetching contract'); + expect(oraInstance.succeed).toHaveBeenCalledWith('Fetching contract successfully!'); + expect(contractInstance).toMatchObject({ address: contractAddress }); + }); + + test('should fetch AElf contract by name', async () => { + // contract Token + const contractInstance = await getContractInstance('AElf.ContractNames.Token', aelf, wallet, oraInstance); + expect(oraInstance.start).toHaveBeenCalledWith('Fetching contract'); + expect(oraInstance.succeed).toHaveBeenCalledWith('Fetching contract successfully!'); + expect(contractInstance).toMatchObject({ address: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx' }); + }); + + test('should fail and exit process if contract retrieval fails', async () => { + const spyFail = jest.spyOn(oraInstance, 'fail').mockReturnValue(); + const spyError = jest.spyOn(plainLogger, 'error').mockReturnValue('Error message'); + const spyExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); + await expect(getContractInstance('invalidAddress', aelf, wallet, oraInstance)); + expect(spyFail).toHaveBeenCalled(); + expect(spyError).toHaveBeenCalled(); + expect(spyExit).toHaveBeenCalledWith(1); + }); + + test('should handle contract address which is not string', async () => { + // contract Token + const contractInstance = await getContractInstance({}, aelf, wallet, oraInstance); + expect(contractInstance).toEqual({}); + }); + }); + + describe('getMethod', () => { + test('should return method if exists in contract', () => { + const contract = { method: jest.fn() }; + const method = getMethod('method', contract); + expect(method).toBe(contract.method); + }); + + test('should throw error if method does not exist in contract', () => { + const contract = {}; + expect(() => getMethod('nonexistentMethod', contract)).toThrow('Not exist method nonexistentMethod'); + }); + + test('should return method directly if not a string', () => { + const contract = {}; + const method = getMethod(null, contract); + expect(method).toBeNull(); + }); + }); + + describe('promptTolerateSeveralTimes', () => { + let oraInstance; + + beforeEach(() => { + oraInstance = { + start: jest.fn(), + fail: jest.fn() + }; + }); + + test('should return valid input according to pattern', async () => { + inquirer.prompt.mockResolvedValueOnce({ input: 'validInput' }); + const processAfterPrompt = jest.fn().mockResolvedValue('processedInput'); + const input = await promptTolerateSeveralTimes( + { + processAfterPrompt, + pattern: /valid/, + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ); + expect(input).toBe('processedInput'); + expect(processAfterPrompt).toHaveBeenCalledWith({ input: 'validInput' }); + }); + + test('should retry prompt if input does not match pattern', async () => { + inquirer.prompt.mockResolvedValue({ input: 'invalidInput' }); + const processAfterPrompt = jest.fn().mockResolvedValue('processedInput'); + await promptTolerateSeveralTimes( + { + processAfterPrompt, + pattern: /valid/, + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ); + expect(inquirer.prompt).toHaveBeenCalledTimes(3); + }); + + test('should exit process if maximum attempts exceeded', async () => { + inquirer.prompt.mockResolvedValue({ input: null }); + const spyFail = jest.spyOn(oraInstance, 'fail').mockReturnValue(); + const spyFatal = jest.spyOn(plainLogger, 'fatal').mockReturnValue(); + const spyExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); + const processAfterPrompt = jest.fn().mockResolvedValue(null); + await promptTolerateSeveralTimes( + { + processAfterPrompt, + pattern: /valid/, + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ); + expect(spyFail).toHaveBeenCalled(); + expect(spyFatal).toHaveBeenCalled(); + expect(spyExit).toHaveBeenCalledWith(1); + }); + test('should handle error pattern', async () => { + await expect( + promptTolerateSeveralTimes( + { + pattern: {}, + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ) + ).rejects.toThrow("param 'pattern' must be a regular expression!"); + }); + + test('should handle error processAfterPrompt', async () => { + await expect( + promptTolerateSeveralTimes( + { + processAfterPrompt: {}, + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ) + ).rejects.toThrow("Param 'processAfterPrompt' must be a function!"); + }); + + test('should handle no pattern', async () => { + inquirer.prompt.mockImplementation(() => { + throw new Error(); + }); + await promptTolerateSeveralTimes( + { + times: 3, + prompt: [{ name: 'input', message: 'Enter input' }] + }, + oraInstance + ); + const spyFail = jest.spyOn(oraInstance, 'fail'); + expect(spyFail).toHaveBeenCalledWith('Failed'); + }); + }); + + describe('getTxResult', () => { + let aelf; + + beforeEach(() => { + aelf = { chain: { getTxResult: jest.fn() } }; + }); + + test('should return tx result when Status is MINED', async () => { + aelf.chain.getTxResult.mockResolvedValueOnce({ Status: 'MINED' }); + const tx = await getTxResult(aelf, 'txId'); + expect(tx).toEqual({ Status: 'MINED' }); + }); + + test('should throw error if Status is not MINED after retries', async () => { + aelf.chain.getTxResult.mockResolvedValue({ Status: 'PENDING' }); + const tx = await getTxResult(aelf, 'txId'); + expect(tx).toEqual({ Status: 'PENDING' }); + }); + + test('should retry if Status is PENDING', async () => { + aelf.chain.getTxResult.mockResolvedValueOnce({ Status: 'PENDING' }).mockResolvedValueOnce({ Status: 'MINED' }); + const tx = await getTxResult(aelf, 'txId'); + expect(tx).toEqual({ Status: 'MINED' }); + }); + + test('should retry if Status is FAILED', async () => { + aelf.chain.getTxResult.mockResolvedValueOnce({ Status: 'FAILED' }); + await expect(getTxResult(aelf, 'txId')).rejects.toEqual({ Status: 'FAILED' }); + }); + }); + + describe('parseJSON', () => { + test('should correctly parse JSON', function () { + expect(parseJSON('{"key": "value"}')).toEqual({ key: 'value' }); + expect(parseJSON('invalid')).toBe('invalid'); + expect(parseJSON('')).toBe(''); + }); + }); + + describe('randomId', () => { + it('should generate a random UUID without dashes', function () { + const uuid = randomId(); + expect(typeof uuid).toBe('string'); + expect(uuid.length).toBe(32); + }); + }); + + describe('getParams', () => { + let oraInstance; + const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); + const contractAddress = '2cVQrFiXNaedBYmUrovmUV2jcF9Hf6AXbh12gWsD4P49NaX99y'; + const wallet = AElf.wallet.getWalletByPrivateKey('9a2c6023e8b2221f4b02f4ccc5128392c1bd968ae45a42fa62848d793fff148f'); + beforeEach(() => { + oraInstance = { + start: jest.fn(), + succeed: jest.fn(), + fail: jest.fn() + }; + }); + test('should get parameters by method', async () => { + const contractInstance = await getContractInstance(contractAddress, aelf, wallet, oraInstance); + const method = getMethod('GetProposal', contractInstance); + inquirer.prompt.mockResolvedValue({ value: 'proposal' }); + const params = await getParams(method); + expect(params).toBe('proposal'); + }); + + test('should get parameters by method with not special params', async () => { + const contractInstance = await getContractInstance(contractAddress, aelf, wallet, oraInstance); + // console.log(Object.keys(contractInstance)); + const method = getMethod('GetMethodFee', contractInstance); + inquirer.prompt.mockResolvedValue({ value: 'method fee' }); + const params = await getParams(method); + expect(params).toEqual({ value: 'method fee' }); + }); + }); + + describe('deserializeLogs', () => { + let oraInstance; + const aelf = new AElf(new AElf.providers.HttpProvider(endpoint)); + beforeEach(() => { + oraInstance = { + start: jest.fn(), + succeed: jest.fn(), + fail: jest.fn() + }; + }); + test('test deserialize log', async () => { + const logs = [ + { + Address: 'ELF_2sGZFRtqQ57F55Z2KvhmoozKrf7ik2htNVQawEAo3Vyvcx9Qwr_tDVW', + Name: '.aelf.Hash', + NonIndexed: 'CgNFTEYQoI/hGQ==' + } + ]; + const result = await deserializeLogs(aelf, logs); + expect(result).toEqual(['454c46']); + }, 40000); + test('test deserialize log with VirtualTransactionCreated', async () => { + const logs = [ + { + Address: '238X6iw1j8YKcHvkDYVtYVbuYk2gJnK8UoNpVCtssynSpVC8hb', + Name: 'VirtualTransactionCreated', + Indexed: [ + 'CiIKIA8J04pLJGNHl4y2KWuBJipdXjtJ2ForrSRRuRx9w2LY', + 'EiIKIAR/b9iJa/+kT2+h9XAdQE0UX9wFZogfPtn9YvtlCnB2', + 'GiIKICeR6ZKlfyjnWhHxOvLArsiw6zXS8EjULrqJAckuA3jc', + 'IghUcmFuc2Zlcg==', + 'MiIKICWmXUMWhKDuXFdYz8/uF7ze4kC5r3i7boxM5Dj+RE4G' + ], + NonIndexed: 'KjAKIgogIKCTibOwFJNFp0zUNEXymkyazYKz8LLwLqOZxEqKRF0SA09NSRiA0NvD9AI=' + } + ]; + const result = await deserializeLogs(aelf, logs); + expect(result).toEqual([ + { + from: '2ytdtA2PDX7VLYWkqf36MQQ8wUtcXWRdpovX7Wxy8tJZXumaY', + methodName: 'Transfer', + params: 'CiIKICCgk4mzsBSTRadM1DRF8ppMms2Cs/Cy8C6jmcRKikRdEgNPTUkYgNDbw/QC', + signatory: 'HaiUnezHpBieiVZNuyQV4uLFspYDGxsEwt8wSFYqGSpXY3CzJ', + to: 'JRmBduh4nXWi1aXgdUsj5gJrzeZb2LxmrAbf7W99faZSvoAaE', + virtualHash: '0f09d38a4b246347978cb6296b81262a5d5e3b49d85a2bad2451b91c7dc362d8' + } + ]); + }); + test('test deserialize log with empty NonIndexed', async () => { + const logs = [ + { + Indexed: ['CiIKIPoq3y6L7T71F5BynCBXISeMFKrCt4QayljkLE4U8St4', 'EiIKIKt0P1P3+jKuU4Y5rSGOfzleHFw0YXn5eNM88jWfUWYR'], + Name: '.aelf.Hash', + Address: 'ELF_2sGZFRtqQ57F55Z2KvhmoozKrf7ik2htNVQawEAo3Vyvcx9Qwr_tDVW' + } + ]; + const result = await deserializeLogs(aelf, logs); + expect(result).toEqual(['0a20fa2adf2e8bed3ef51790729c205721278c14aac2b7841aca58e42c4e14f12b78']); + }); + test('test deserialize log with empty logs', async () => { + const result = await deserializeLogs(aelf); + expect(result).toEqual(null); + }); + }); +}); diff --git a/test/utils/wallet.test.js b/test/utils/wallet.test.js new file mode 100644 index 0000000..c08ea56 --- /dev/null +++ b/test/utils/wallet.test.js @@ -0,0 +1,81 @@ +import inquirer from 'inquirer'; +import fs from 'fs'; +import { mkdirpSync } from 'mkdirp'; +import { getWallet, saveKeyStore } from '../../src/utils/wallet'; +import { endpoint, account, password, dataDir } from '../constants.js'; +import keyJSON from '../dataDir/aelf/keys/GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk.json'; + +jest.mock('inquirer'); +jest.mock('mkdirp'); + +describe('wallet', () => { + describe('getWallet', () => { + test('should get wallet', () => { + const result = getWallet(dataDir, account, password); + expect(result.privateKey).toBe('9a2c6023e8b2221f4b02f4ccc5128392c1bd968ae45a42fa62848d793fff148f'); + expect(result.mnemonic).toBe('impact fork bulk museum swap design draw arctic load option ticket across'); + }); + test('should handle error', () => { + expect(() => getWallet(dataDir, 'test', password)).toThrow('Make sure you entered the correct account address'); + expect(() => getWallet(dataDir, account, 'test')).toThrow('Make sure you entered the correct password'); + }); + }); + describe('saveKeyStore', () => { + let existsSyncMock; + let writeFileSyncMock; + const wallet = getWallet(dataDir, account, password); + const keyStorePath = `${dataDir}/keys/${wallet.address}.json`; + + beforeEach(() => { + jest.clearAllMocks(); + inquirer.prompt.mockResolvedValue({ + password: '1234*Qwer', + confirmPassword: '1234*Qwer' + }); + }); + + beforeAll(() => { + // Mock fs methods + existsSyncMock = jest.spyOn(fs, 'existsSync').mockImplementation(() => true); + writeFileSyncMock = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); + }); + + afterAll(() => { + // Restore fs methods + existsSyncMock.mockRestore(); + writeFileSyncMock.mockRestore(); + }); + + test('should save keystore file and return its path', async () => { + existsSyncMock.mockReturnValueOnce(false); + const result = await saveKeyStore(wallet, dataDir); + expect(mkdirpSync).toHaveBeenCalled(); + expect(writeFileSyncMock).toHaveBeenCalled(); + expect(result).toBe(keyStorePath); + }); + + test('should throw error if passwords do not match', async () => { + inquirer.prompt.mockResolvedValueOnce({ + password: 'test-password', + confirmPassword: 'wrong-password' + }); + await expect(saveKeyStore(wallet, dataDir)).rejects.toThrow('Passwords are different'); + }); + + test('should throw error if password is too short', async () => { + inquirer.prompt.mockResolvedValueOnce({ + password: 'short', + confirmPassword: 'short' + }); + await expect(saveKeyStore(wallet, dataDir)).rejects.toThrow('password is too short'); + }); + + test('should not create directory if it already exists', async () => { + existsSyncMock.mockReturnValueOnce(true); + const result = await saveKeyStore(wallet, dataDir); + expect(mkdirpSync).not.toHaveBeenCalled(); + expect(writeFileSyncMock).toHaveBeenCalled(); + expect(result).toBe(keyStorePath); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 9aec3fc..86388a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3343,6 +3343,17 @@ emoji-regex@^9.2.2: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +engine.io-client@~6.5.2: + version "6.5.3" + resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz#4cf6fa24845029b238f83c628916d9149c399bc5" + integrity sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + engine.io-parser@~5.2.1: version "5.2.2" resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49" @@ -6765,6 +6776,16 @@ socket.io-adapter@~2.5.2: debug "~4.3.4" ws "~8.11.0" +socket.io-client@^4.7.5: + version "4.7.5" + resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz#919be76916989758bdc20eec63f7ee0ae45c05b7" + integrity sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.5.2" + socket.io-parser "~4.2.4" + socket.io-parser@~4.2.4: version "4.2.4" resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" @@ -7536,6 +7557,11 @@ xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz#1efba19425e73be1bc6f2a6ceb52a3d2c884c0c9" integrity sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ== +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + xmlhttprequest@^1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"