@dolaned/wallet-sdk-ts
Version:
Wallet SDK for the Nexa blockchain
341 lines (288 loc) • 13.1 kB
text/typescript
import { describe, test, expect, beforeEach, vi } from 'vitest'
import { HDPrivateKey, Networks } from 'libnexa-ts'
import AccountStore from '../../../src/wallet/accounts/AccountStore'
import { AccountType } from '../../../src/utils/WalletUtils'
import { BaseAccount } from '../../../src/wallet/accounts/interfaces/BaseAccountInterface'
import DAppAccount from '../../../src/wallet/accounts/models/DappAccount'
import VaultAccount from '../../../src/wallet/accounts/models/VaultAccount'
import DefaultAccount from '../../../src/wallet/accounts/models/DefaultAccount'
// Mock the WalletUtils
vi.mock('../../../src/utils/WalletUtils', () => ({
AccountType: {
NEXA_ACCOUNT: 0,
VAULT_ACCOUNT: 1,
DAPP_ACCOUNT: 2
},
getNextAccountIndex: vi.fn().mockResolvedValue(0),
generateAccountKey: vi.fn().mockReturnValue({
deriveChild: vi.fn().mockReturnValue({
privateKey: {
toAddress: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('nexatest:address123')
})
}
})
}),
generateKeyAndAddress: vi.fn().mockReturnValue({
key: {
privateKey: {
toAddress: vi.fn().mockReturnValue({
toString: vi.fn().mockReturnValue('nexatest:address123')
})
}
},
address: 'nexatest:address123',
balance: '0',
tokensBalance: {}
}),
generateKeysAndAddresses: vi.fn().mockReturnValue({
receiveKeys: [{
key: { privateKey: {} },
address: 'nexatest:receive123',
balance: '0',
tokensBalance: {}
}],
changeKeys: [{
key: { privateKey: {} },
address: 'nexatest:change123',
balance: '0',
tokensBalance: {}
}]
})
}))
// Mock the account classes
vi.mock('../../../src/wallet/accounts/models/DappAccount', () => ({
default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
purpose,
index,
addressKey,
accountKeys: {
receiveKeys: [addressKey],
changeKeys: []
},
loadBalances: vi.fn().mockResolvedValue(undefined),
getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
}))
}))
vi.mock('../../../src/wallet/accounts/models/VaultAccount', () => ({
default: vi.fn().mockImplementation((purpose, index, addressKey) => ({
purpose,
index,
addressKey,
accountKeys: {
receiveKeys: [addressKey],
changeKeys: []
},
loadBalances: vi.fn().mockResolvedValue(undefined),
getAccountStoreKey: vi.fn().mockReturnValue(`${purpose}.${index}`)
}))
}))
vi.mock('../../../src/wallet/accounts/models/DefaultAccount', () => ({
default: vi.fn().mockImplementation((index, indexes, keys) => ({
index,
indexes,
accountKeys: keys,
loadBalances: vi.fn().mockResolvedValue(undefined),
getAccountStoreKey: vi.fn().mockReturnValue(String(index))
}))
}))
describe('AccountStore', () => {
let accountStore: AccountStore
let mockMasterKey: HDPrivateKey
beforeEach(() => {
accountStore = new AccountStore()
mockMasterKey = {} as HDPrivateKey
vi.clearAllMocks()
})
describe('constructor', () => {
test('should create empty account store', () => {
expect(accountStore).toBeInstanceOf(AccountStore)
expect(accountStore.listAccounts().size).toBe(0)
})
})
describe('createAccount', () => {
test('should create DApp account', async () => {
const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
expect(account).toBeDefined()
expect(DAppAccount).toHaveBeenCalledWith(2, 0, expect.any(Object))
expect(accountStore.listAccounts().size).toBe(1)
})
test('should create Vault account', async () => {
const account = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
expect(account).toBeDefined()
expect(VaultAccount).toHaveBeenCalledWith(1, 0, expect.any(Object))
expect(accountStore.listAccounts().size).toBe(1)
})
test('should create Default NEXA account', async () => {
const account = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
expect(account).toBeDefined()
expect(DefaultAccount).toHaveBeenCalledWith(0, expect.any(Object), expect.any(Object))
expect(accountStore.listAccounts().size).toBe(1)
})
test('should return existing account if already created', async () => {
const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
const account2 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
expect(account1).toBe(account2)
expect(accountStore.listAccounts().size).toBe(1)
})
})
describe('getAccountStoreKey', () => {
test('should generate correct key for DApp account', () => {
const key = accountStore['getAccountStoreKey'](AccountType.DAPP_ACCOUNT, 0)
expect(key).toBe('2.0')
})
test('should generate correct key for Vault account', () => {
const key = accountStore['getAccountStoreKey'](AccountType.VAULT_ACCOUNT, 1)
expect(key).toBe('1.1')
})
test('should generate correct key for Default account', () => {
const key = accountStore['getAccountStoreKey'](AccountType.NEXA_ACCOUNT, 0)
expect(key).toBe('0')
})
})
describe('findKeyForAddress', () => {
test('should find key for existing address', async () => {
const mockAccount = {
accountKeys: {
receiveKeys: [{
address: 'nexatest:target123',
key: { privateKey: 'mock-key' }
}],
changeKeys: [{
address: 'nexatest:change123',
key: { privateKey: 'mock-key-2' }
}]
}
}
accountStore['_accounts'].set('test', mockAccount as any)
const result = accountStore.findKeyForAddress('nexatest:target123')
expect(result).toBeDefined()
expect(result?.address).toBe('nexatest:target123')
})
test('should return null for non-existent address', () => {
const result = accountStore.findKeyForAddress('nexatest:nonexistent')
expect(result).toBeNull()
})
test('should search through both receive and change keys', async () => {
const mockAccount = {
accountKeys: {
receiveKeys: [{
address: 'nexatest:receive123',
key: { privateKey: 'mock-key' }
}],
changeKeys: [{
address: 'nexatest:change123',
key: { privateKey: 'mock-key-2' }
}]
}
}
accountStore['_accounts'].set('test', mockAccount as any)
const receiveResult = accountStore.findKeyForAddress('nexatest:receive123')
const changeResult = accountStore.findKeyForAddress('nexatest:change123')
expect(receiveResult?.address).toBe('nexatest:receive123')
expect(changeResult?.address).toBe('nexatest:change123')
})
})
describe('importAccount', () => {
test('should import new account', () => {
const mockAccount = {
getAccountStoreKey: vi.fn().mockReturnValue('imported.0')
} as any
accountStore.importAccount(mockAccount)
expect(accountStore.listAccounts().size).toBe(1)
expect(accountStore.getAccount('imported.0')).toBe(mockAccount)
})
test('should throw error for duplicate account', () => {
const mockAccount = {
getAccountStoreKey: vi.fn().mockReturnValue('duplicate.0')
} as any
accountStore.importAccount(mockAccount)
expect(() => {
accountStore.importAccount(mockAccount)
}).toThrow('Account already exists!')
})
})
describe('exportAccount', () => {
test('should export existing account', () => {
const mockAccount = {
getAccountStoreKey: vi.fn().mockReturnValue('export.0')
} as any
accountStore['_accounts'].set('export.0', mockAccount)
const exported = accountStore.exportAccount('export.0')
expect(exported).toBe(mockAccount)
})
test('should throw error for non-existent account', () => {
expect(() => {
accountStore.exportAccount('nonexistent')
}).toThrow('Cannot find account!')
})
})
describe('removeAccount', () => {
test('should remove existing account', () => {
const mockAccount = {
getAccountStoreKey: vi.fn().mockReturnValue('remove.0')
} as any
accountStore['_accounts'].set('remove.0', mockAccount)
expect(accountStore.listAccounts().size).toBe(1)
accountStore.removeAccount('remove.0')
expect(accountStore.listAccounts().size).toBe(0)
})
test('should throw error for non-existent account', () => {
expect(() => {
accountStore.removeAccount('nonexistent')
}).toThrow('Cannot find account!')
})
})
describe('listAccounts', () => {
test('should return all accounts', async () => {
await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
const accounts = accountStore.listAccounts()
expect(accounts.size).toBe(2)
})
test('should return empty map when no accounts', () => {
const accounts = accountStore.listAccounts()
expect(accounts.size).toBe(0)
expect(accounts).toBeInstanceOf(Map)
})
})
describe('getAccount', () => {
test('should return account by index', async () => {
const account = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
const retrieved = accountStore.getAccount('2.0')
expect(retrieved).toBe(account)
})
test('should return undefined for non-existent account', () => {
const retrieved = accountStore.getAccount('nonexistent')
expect(retrieved).toBeUndefined()
})
})
describe('error handling', () => {
test('should handle account creation errors gracefully', async () => {
// Mock getNextAccountIndex to throw an error
const { getNextAccountIndex } = await import('../../../src/utils/WalletUtils')
vi.mocked(getNextAccountIndex).mockRejectedValueOnce(new Error('Network error'))
await expect(
accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
).rejects.toThrow('Network error')
})
})
describe('integration tests', () => {
test('should handle multiple account types', async () => {
const dappAccount = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
const vaultAccount = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
const nexaAccount = await accountStore.createAccount(AccountType.NEXA_ACCOUNT, mockMasterKey)
expect(accountStore.listAccounts().size).toBe(3)
expect(accountStore.getAccount('2.0')).toBe(dappAccount)
expect(accountStore.getAccount('1.0')).toBe(vaultAccount)
expect(accountStore.getAccount('0')).toBe(nexaAccount)
})
test('should maintain account independence', async () => {
const account1 = await accountStore.createAccount(AccountType.DAPP_ACCOUNT, mockMasterKey)
const account2 = await accountStore.createAccount(AccountType.VAULT_ACCOUNT, mockMasterKey)
accountStore.removeAccount('2.0')
expect(accountStore.getAccount('2.0')).toBeUndefined()
expect(accountStore.getAccount('1.0')).toBe(account2)
})
})
})