From 7cc02faef4e2e080c8f6778dd02c20de68eb6609 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:26:42 -0300 Subject: [PATCH 01/11] refactor!: unify changeServer to async BREAKING CHANGE: HathorWallet.changeServer now returns Promise instead of void, matching the service facade signature. Added changeServer to IHathorWallet. Co-Authored-By: Claude Opus 4.6 (1M context) --- __tests__/integration/hathorwallet_others.test.ts | 2 +- src/new/wallet.ts | 2 +- src/wallet/types.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/__tests__/integration/hathorwallet_others.test.ts b/__tests__/integration/hathorwallet_others.test.ts index 6145a5444..74591e0e3 100644 --- a/__tests__/integration/hathorwallet_others.test.ts +++ b/__tests__/integration/hathorwallet_others.test.ts @@ -1678,7 +1678,7 @@ describe('internal methods', () => { it('should change servers', async () => { // Changing from our integration test privatenet to the testnet - gWallet.changeServer('https://node1.testnet.hathor.network/v1a/'); + await gWallet.changeServer('https://node1.testnet.hathor.network/v1a/'); const serverChangeTime = Date.now().valueOf(); await delay(100); diff --git a/src/new/wallet.ts b/src/new/wallet.ts index ad7f0b651..6c31b23a4 100644 --- a/src/new/wallet.ts +++ b/src/new/wallet.ts @@ -470,7 +470,7 @@ class HathorWallet extends EventEmitter { * @memberof HathorWallet * @inner * */ - changeServer(newServer: string): void { + async changeServer(newServer: string): Promise { this.storage.config.setServerUrl(newServer); } diff --git a/src/wallet/types.ts b/src/wallet/types.ts index e714e0d3e..9f70d1652 100644 --- a/src/wallet/types.ts +++ b/src/wallet/types.ts @@ -417,6 +417,7 @@ export interface IHathorWallet { checkPin(pin: string): Promise; checkPassword(password: string): Promise; checkPinAndPassword(pin: string, password: string): Promise; + changeServer(newServer: string): Promise; getServerUrl(): string; getNetwork(): string; getAddressPathForIndex(index: number): Promise; From 60dd062217b215785cb6e2f4fbecc0bc0bd9a206 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:30:02 -0300 Subject: [PATCH 02/11] test: extract shared network query tests Move getServerUrl, getNetwork, getNetworkObject and getVersionData tests into shared/internal.test.ts, running against both fullnode and service facades via describe.each. Includes direct fullnode /version cross-check for both facades. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration/hathorwallet_others.test.ts | 32 ----- __tests__/integration/shared/internal.test.ts | 123 ++++++++++++++++++ .../integration/walletservice_facade.test.ts | 79 +---------- 3 files changed, 124 insertions(+), 110 deletions(-) create mode 100644 __tests__/integration/shared/internal.test.ts diff --git a/__tests__/integration/hathorwallet_others.test.ts b/__tests__/integration/hathorwallet_others.test.ts index 74591e0e3..5dbc695ab 100644 --- a/__tests__/integration/hathorwallet_others.test.ts +++ b/__tests__/integration/hathorwallet_others.test.ts @@ -12,7 +12,6 @@ import { NATIVE_TOKEN_UID, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../src/co import { FULLNODE_NETWORK_NAME, FULLNODE_URL, - NETWORK_NAME, WALLET_CONSTANTS, } from './configuration/test-constants'; import dateFormatter from '../../src/utils/date'; @@ -1645,37 +1644,6 @@ describe('internal methods', () => { expect(gWallet.debug).toStrictEqual(false); }); - it('should test network-related methods', async () => { - // GetServerUrl fetching from the live fullnode connection - expect(await gWallet.getServerUrl()).toStrictEqual(FULLNODE_URL); - expect(await gWallet.getNetwork()).toStrictEqual(NETWORK_NAME); - expect(await gWallet.getNetworkObject()).toMatchObject({ - name: NETWORK_NAME, - versionBytes: { p2pkh: 73, p2sh: 135 }, // Calculated for the privnet.py config file - bitcoreNetwork: { - name: expect.stringContaining(NETWORK_NAME), - alias: 'test', // this is the alias for the testnet network - pubkeyhash: 73, - scripthash: 135, - }, - }); - - // GetVersionData fetching from the live fullnode server - expect(await gWallet.getVersionData()).toMatchObject({ - timestamp: expect.any(Number), - version: expect.any(String), - network: FULLNODE_NETWORK_NAME, - minWeight: expect.any(Number), - minTxWeight: expect.any(Number), - minTxWeightCoefficient: expect.any(Number), - minTxWeightK: expect.any(Number), - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: expect.any(Number), - maxNumberInputs: 255, - maxNumberOutputs: 255, - }); - }); - it('should change servers', async () => { // Changing from our integration test privatenet to the testnet await gWallet.changeServer('https://node1.testnet.hathor.network/v1a/'); diff --git a/__tests__/integration/shared/internal.test.ts b/__tests__/integration/shared/internal.test.ts new file mode 100644 index 000000000..32c632495 --- /dev/null +++ b/__tests__/integration/shared/internal.test.ts @@ -0,0 +1,123 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import axios from 'axios'; +import type { FuzzyWalletType, IWalletTestAdapter } from '../adapters/types'; +import { FullnodeWalletTestAdapter } from '../adapters/fullnode.adapter'; +import { ServiceWalletTestAdapter } from '../adapters/service.adapter'; +import { FULLNODE_NETWORK_NAME, FULLNODE_URL, NETWORK_NAME } from '../configuration/test-constants'; +import Network from '../../../src/models/network'; +import { loggers } from '../utils/logger.util'; + +// XXX: onConnectionChangedState has different behavior between facades +// (fullnode calls reloadStorage/processHistory, service emits 'reload-data'). +// It needs refactoring before it can be tested here as a shared test. + +const adapters: IWalletTestAdapter[] = [ + new FullnodeWalletTestAdapter(), + new ServiceWalletTestAdapter(), +]; + +/** + * Minimum expected shape for getVersionData across both facades. + * Both facades should query the fullnode /version endpoint and return + * the same data. If a backend inconsistency is found for a specific + * facade, adjust the corresponding adapter's `versionDataOverrides` + * or skip individual fields here rather than duplicating the test. + */ +const baseVersionDataExpectation = { + timestamp: expect.any(Number), + version: expect.any(String), + network: FULLNODE_NETWORK_NAME, + minWeight: expect.any(Number), + minTxWeight: expect.any(Number), + minTxWeightCoefficient: expect.any(Number), + minTxWeightK: expect.any(Number), + tokenDepositPercentage: expect.any(Number), + rewardSpendMinBlocks: expect.any(Number), + maxNumberInputs: expect.any(Number), + maxNumberOutputs: expect.any(Number), +}; + +describe.each(adapters)('[Shared] internal methods — $name', adapter => { + beforeAll(async () => { + await adapter.suiteSetup(); + }); + + afterAll(async () => { + await adapter.suiteTeardown(); + }); + + describe('network query methods', () => { + let wallet: FuzzyWalletType; + + beforeAll(async () => { + const result = await adapter.createWallet(); + wallet = result.wallet; + }); + + afterAll(async () => { + await adapter.stopWallet(wallet); + }); + + it('getServerUrl returns the configured fullnode URL', () => { + expect(wallet.getServerUrl()).toBe(FULLNODE_URL); + }); + + it('getNetwork returns the correct network name', () => { + expect(wallet.getNetwork()).toBe(NETWORK_NAME); + }); + + it('getNetworkObject returns a Network instance with correct properties', () => { + const networkObj = wallet.getNetworkObject(); + expect(networkObj).toBeInstanceOf(Network); + expect(networkObj.name).toBe(NETWORK_NAME); + expect(networkObj).toMatchObject({ + versionBytes: { p2pkh: 73, p2sh: 135 }, + bitcoreNetwork: { + name: expect.stringContaining(NETWORK_NAME), + alias: 'test', + pubkeyhash: 73, + scripthash: 135, + }, + }); + }); + + it('getVersionData returns valid version info from the fullnode', async () => { + const versionData = await wallet.getVersionData(); + expect(versionData).toMatchObject(baseVersionDataExpectation); + }); + + it('getVersionData matches data from a direct fullnode request', async () => { + const versionData = await wallet.getVersionData(); + + const directResponse = await axios + .get('version', { + baseURL: FULLNODE_URL, + headers: { 'Content-Type': 'application/json' }, + }) + .catch(e => { + loggers.test!.log(`Received an error on /version: ${e}`); + if (e.response) { + return e.response; + } + return {}; + }); + expect(directResponse.status).toBe(200); + + // Both facades should return data consistent with the fullnode. + // Compare only the fields defined in FullNodeVersionData to + // tolerate extra fields the backend may include. + const fullnodeData = directResponse.data; + for (const key of Object.keys(baseVersionDataExpectation)) { + expect(versionData).toHaveProperty(key); + expect(fullnodeData).toHaveProperty(key); + expect(versionData[key]).toStrictEqual(fullnodeData[key]); + } + }); + }); +}); diff --git a/__tests__/integration/walletservice_facade.test.ts b/__tests__/integration/walletservice_facade.test.ts index 95c9a827a..f08ef85fb 100644 --- a/__tests__/integration/walletservice_facade.test.ts +++ b/__tests__/integration/walletservice_facade.test.ts @@ -1,16 +1,9 @@ -import axios from 'axios'; import config from '../../src/config'; import { loggers } from './utils/logger.util'; import HathorWalletServiceWallet from '../../src/wallet/wallet'; import { CreateTokenTransaction, FeeHeader, Output, transactionUtils } from '../../src'; -import { - FULLNODE_NETWORK_NAME, - FULLNODE_URL, - NETWORK_NAME, - WALLET_CONSTANTS, -} from './configuration/test-constants'; +import { WALLET_CONSTANTS } from './configuration/test-constants'; import { NATIVE_TOKEN_UID, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../src/constants'; -import Network from '../../src/models/network'; import { buildWalletInstance, emptyWallet, @@ -162,76 +155,6 @@ afterAll(async () => { await GenesisWalletServiceHelper.stop(); }); -describe('wallet public methods', () => { - beforeEach(async () => { - ({ wallet } = buildWalletInstance({ words: emptyWallet.words })); - await wallet.start({ pinCode, password }); - }); - - afterEach(async () => { - if (wallet) { - await wallet.stop({ cleanStorage: true }); - } - }); - - it('getServerUrl returns the configured base URL', () => { - expect(wallet.getServerUrl()).toBe(FULLNODE_URL); - }); - - it('getVersionData returns valid version info', async () => { - const versionData = await wallet.getVersionData(); - expect(versionData).toBeDefined(); - expect(versionData).toEqual( - expect.objectContaining({ - timestamp: expect.any(Number), - version: expect.any(String), - network: FULLNODE_NETWORK_NAME, - minWeight: expect.any(Number), - minTxWeight: expect.any(Number), - minTxWeightCoefficient: expect.any(Number), - minTxWeightK: expect.any(Number), - tokenDepositPercentage: expect.any(Number), - rewardSpendMinBlocks: expect.any(Number), - maxNumberInputs: expect.any(Number), - maxNumberOutputs: expect.any(Number), - decimalPlaces: expect.any(Number), - nativeTokenName: expect.any(String), - nativeTokenSymbol: expect.any(String), - }) - ); - - // Make sure it contains the same data as a direct fullnode request - const fullnodeResponse = await axios - .get('version', { - baseURL: config.getWalletServiceBaseUrl(), - headers: { - 'Content-Type': 'application/json', - }, - }) - .catch(e => { - loggers.test!.log(`Received an error on /version: ${e}`); - if (e.response) { - return e.response; - } - return {}; - }); - expect(fullnodeResponse.status).toBe(200); - expect(fullnodeResponse.data?.success).toBe(true); - - expect(versionData).toEqual(fullnodeResponse.data.data); - }); - - it('getNetwork returns the correct network name', () => { - expect(wallet.getNetwork()).toBe(NETWORK_NAME); - }); - - it('getNetworkObject returns a Network instance with correct name', () => { - const networkObj = wallet.getNetworkObject(); - expect(networkObj).toBeInstanceOf(Network); - expect(networkObj.name).toBe(NETWORK_NAME); - }); -}); - describe('empty wallet address methods', () => { const knownAddresses = emptyWallet.addresses; const unknownAddress = WALLET_CONSTANTS.miner.addresses[0]; From a764b06369e2ad1d6c117935d07c9b2197d90aec Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:31:07 -0300 Subject: [PATCH 03/11] test: extract shared server change tests Move changeServer tests into shared/server_changes.test.ts, running against both facades. The test now uses try/finally to guarantee server URL revert even on assertion failure. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration/hathorwallet_others.test.ts | 26 +------ .../integration/shared/server_changes.test.ts | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 __tests__/integration/shared/server_changes.test.ts diff --git a/__tests__/integration/hathorwallet_others.test.ts b/__tests__/integration/hathorwallet_others.test.ts index 5dbc695ab..e4d8804a3 100644 --- a/__tests__/integration/hathorwallet_others.test.ts +++ b/__tests__/integration/hathorwallet_others.test.ts @@ -9,11 +9,7 @@ import { waitUntilNextTimestamp, } from './helpers/wallet.helper'; import { NATIVE_TOKEN_UID, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../src/constants'; -import { - FULLNODE_NETWORK_NAME, - FULLNODE_URL, - WALLET_CONSTANTS, -} from './configuration/test-constants'; +import { WALLET_CONSTANTS } from './configuration/test-constants'; import dateFormatter from '../../src/utils/date'; import { AddressError } from '../../src/errors'; import { precalculationHelpers } from './helpers/wallet-precalculation.helper'; @@ -1644,26 +1640,6 @@ describe('internal methods', () => { expect(gWallet.debug).toStrictEqual(false); }); - it('should change servers', async () => { - // Changing from our integration test privatenet to the testnet - await gWallet.changeServer('https://node1.testnet.hathor.network/v1a/'); - const serverChangeTime = Date.now().valueOf(); - await delay(100); - - // Validating the server change with getVersionData - let networkData = await gWallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); - expect(networkData.network).toMatch(/^testnet.*/); - - await gWallet.changeServer(FULLNODE_URL); - await delay(100); - - // Reverting to the privatenet - networkData = await gWallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime + 200); - expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); - }); - it('should reload the storage', async () => { await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10n); const spy = jest.spyOn(hWallet.storage, 'processHistory'); diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts new file mode 100644 index 000000000..993487717 --- /dev/null +++ b/__tests__/integration/shared/server_changes.test.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { FuzzyWalletType, IWalletTestAdapter } from '../adapters/types'; +import { delay } from '../utils/core.util'; +import { FullnodeWalletTestAdapter } from '../adapters/fullnode.adapter'; +import { ServiceWalletTestAdapter } from '../adapters/service.adapter'; +import { FULLNODE_NETWORK_NAME, FULLNODE_URL } from '../configuration/test-constants'; + +const adapters: IWalletTestAdapter[] = [ + new FullnodeWalletTestAdapter(), + new ServiceWalletTestAdapter(), +]; + +describe.each(adapters)('[Shared] server changes — $name', adapter => { + beforeAll(async () => { + await adapter.suiteSetup(); + }); + + afterAll(async () => { + await adapter.suiteTeardown(); + }); + + describe('changeServer', () => { + let wallet: FuzzyWalletType; + + beforeAll(async () => { + const result = await adapter.createWallet(); + wallet = result.wallet; + }); + + afterAll(async () => { + await adapter.stopWallet(wallet); + }); + + it('should change to a different server and revert', async () => { + const testnetUrl = 'https://node1.testnet.hathor.network/v1a/'; + + // Changing from our integration test privatenet to the testnet + await wallet.changeServer(testnetUrl); + const serverChangeTime = Date.now().valueOf(); + + try { + await delay(100); + + // Validating the server change with getVersionData + let networkData = await wallet.getVersionData(); + expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); + expect(networkData.network).toMatch(/^testnet.*/); + } finally { + // Always revert to the original server, even if assertions fail + await wallet.changeServer(FULLNODE_URL); + } + + await delay(100); + + // Verifying the revert to the privatenet + const networkData = await wallet.getVersionData(); + expect(networkData.timestamp).toBeGreaterThan(serverChangeTime + 200); + expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); + }); + }); +}); From aeb370c8a90982475b91eaaaf5ab26fb56337356 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:32:22 -0300 Subject: [PATCH 04/11] test: extract fullnode-specific internal tests Move debug methods and storage reload tests into fullnode-specific/internal.test.ts. Removes the entire 'internal methods' describe block from the monolithic hathorwallet_others.test.ts file. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../fullnode-specific/internal.test.ts | 57 +++++++++++++++++++ .../integration/hathorwallet_others.test.ts | 38 ------------- 2 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 __tests__/integration/fullnode-specific/internal.test.ts diff --git a/__tests__/integration/fullnode-specific/internal.test.ts b/__tests__/integration/fullnode-specific/internal.test.ts new file mode 100644 index 000000000..caefbd3f3 --- /dev/null +++ b/__tests__/integration/fullnode-specific/internal.test.ts @@ -0,0 +1,57 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Fullnode-facade internal method tests. + * + * Tests HathorWallet-only features: debug mode toggling and storage reload + * via onConnectionChangedState. These are side-effect tests that mutate + * wallet state. + * + * Shared internal tests live in `shared/internal.test.ts`. + * Shared server change tests live in `shared/server_changes.test.ts`. + */ + +import HathorWallet from '../../../src/new/wallet'; +import { ConnectionState } from '../../../src/wallet/types'; +import { GenesisWalletHelper } from '../helpers/genesis-wallet.helper'; +import { generateWalletHelper } from '../helpers/wallet.helper'; + +describe('[Fullnode] internal methods', () => { + let gWallet: HathorWallet; + let hWallet: HathorWallet; + + beforeAll(async () => { + const { hWallet: ghWallet } = await GenesisWalletHelper.getSingleton(); + gWallet = ghWallet; + hWallet = await generateWalletHelper(); + }); + + afterAll(async () => { + await hWallet.stop(); + await GenesisWalletHelper.clearListeners(); + await gWallet.stop(); + }); + + it('should test the debug methods', async () => { + expect(gWallet.debug).toStrictEqual(false); + + gWallet.enableDebugMode(); + expect(gWallet.debug).toStrictEqual(true); + + gWallet.disableDebugMode(); + expect(gWallet.debug).toStrictEqual(false); + }); + + it('should reload the storage', async () => { + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10n); + const spy = jest.spyOn(hWallet.storage, 'processHistory'); + // Simulate that we received an event of the connection becoming active + await hWallet.onConnectionChangedState(ConnectionState.CONNECTED); + expect(spy).toHaveBeenCalledTimes(1); + }); +}); diff --git a/__tests__/integration/hathorwallet_others.test.ts b/__tests__/integration/hathorwallet_others.test.ts index e4d8804a3..bc086b753 100644 --- a/__tests__/integration/hathorwallet_others.test.ts +++ b/__tests__/integration/hathorwallet_others.test.ts @@ -13,7 +13,6 @@ import { WALLET_CONSTANTS } from './configuration/test-constants'; import dateFormatter from '../../src/utils/date'; import { AddressError } from '../../src/errors'; import { precalculationHelpers } from './helpers/wallet-precalculation.helper'; -import { ConnectionState } from '../../src/wallet/types'; import HathorWallet from '../../src/new/wallet'; import { MemoryStore } from '../../src/storage'; import { IHistoryTx } from '../../src/types'; @@ -1612,43 +1611,6 @@ describe('getAuthorityUtxos', () => { }); }); -// This section tests methods that have side effects impacting the whole wallet. Executing it last. -describe('internal methods', () => { - /** @type HathorWallet */ - let gWallet; - /** @type HathorWallet */ - let hWallet; - beforeAll(async () => { - const { hWallet: ghWallet } = await GenesisWalletHelper.getSingleton(); - gWallet = ghWallet; - hWallet = await generateWalletHelper(); - }); - - afterAll(async () => { - hWallet.stop(); - await GenesisWalletHelper.clearListeners(); - await gWallet.stop(); - }); - - it('should test the debug methods', async () => { - expect(gWallet.debug).toStrictEqual(false); - - gWallet.enableDebugMode(); - expect(gWallet.debug).toStrictEqual(true); - - gWallet.disableDebugMode(); - expect(gWallet.debug).toStrictEqual(false); - }); - - it('should reload the storage', async () => { - await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10n); - const spy = jest.spyOn(hWallet.storage, 'processHistory'); - // Simulate that we received an event of the connection becoming active - await hWallet.onConnectionChangedState(ConnectionState.CONNECTED); - expect(spy).toHaveBeenCalledTimes(1); - }); -}); - describe('index-limit address scanning policy', () => { /** @type HathorWallet */ let hWallet; From 52195b78feaca7c0ce5dd4bbfd0c2aa21007454b Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:33:45 -0300 Subject: [PATCH 05/11] chore: fix lint warnings from extraction Remove unused config/loggers imports from walletservice_facade.test.ts. Apply eslint autofix for let-to-const in server_changes.test.ts. Co-Authored-By: Claude Opus 4.6 (1M context) --- __tests__/integration/shared/server_changes.test.ts | 2 +- __tests__/integration/walletservice_facade.test.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts index 993487717..5d3e2a4af 100644 --- a/__tests__/integration/shared/server_changes.test.ts +++ b/__tests__/integration/shared/server_changes.test.ts @@ -48,7 +48,7 @@ describe.each(adapters)('[Shared] server changes — $name', adapter => { await delay(100); // Validating the server change with getVersionData - let networkData = await wallet.getVersionData(); + const networkData = await wallet.getVersionData(); expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); expect(networkData.network).toMatch(/^testnet.*/); } finally { diff --git a/__tests__/integration/walletservice_facade.test.ts b/__tests__/integration/walletservice_facade.test.ts index f08ef85fb..7b8146312 100644 --- a/__tests__/integration/walletservice_facade.test.ts +++ b/__tests__/integration/walletservice_facade.test.ts @@ -1,5 +1,3 @@ -import config from '../../src/config'; -import { loggers } from './utils/logger.util'; import HathorWalletServiceWallet from '../../src/wallet/wallet'; import { CreateTokenTransaction, FeeHeader, Output, transactionUtils } from '../../src'; import { WALLET_CONSTANTS } from './configuration/test-constants'; From 24070dd7913390e4b744314793470735f8a8c96e Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 12:58:31 -0300 Subject: [PATCH 06/11] fix: adapt tests to facade semantics Fix getVersionData cross-check to map snake_case keys from the raw fullnode API to camelCase facade keys. Split server_changes into shared (portable contract) and fullnode-specific (getVersionData validation). Add originalServerUrl to IWalletTestAdapter for safe changeServer revert across facades. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration/adapters/fullnode.adapter.ts | 4 +- .../integration/adapters/service.adapter.ts | 2 + __tests__/integration/adapters/types.ts | 7 +++ .../fullnode-specific/server_changes.test.ts | 62 +++++++++++++++++++ __tests__/integration/shared/internal.test.ts | 28 ++++++--- .../integration/shared/server_changes.test.ts | 51 +++++++-------- 6 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 __tests__/integration/fullnode-specific/server_changes.test.ts diff --git a/__tests__/integration/adapters/fullnode.adapter.ts b/__tests__/integration/adapters/fullnode.adapter.ts index 65233fc83..d7870f9af 100644 --- a/__tests__/integration/adapters/fullnode.adapter.ts +++ b/__tests__/integration/adapters/fullnode.adapter.ts @@ -21,7 +21,7 @@ import { import { GenesisWalletHelper } from '../helpers/genesis-wallet.helper'; import { precalculationHelpers } from '../helpers/wallet-precalculation.helper'; import type { WalletStopOptions } from '../../../src/new/types'; -import { NETWORK_NAME } from '../configuration/test-constants'; +import { FULLNODE_URL, NETWORK_NAME } from '../configuration/test-constants'; import type { FuzzyWalletType, IWalletTestAdapter, @@ -51,6 +51,8 @@ export class FullnodeWalletTestAdapter implements IWalletTestAdapter { defaultPassword = DEFAULT_PASSWORD; + originalServerUrl = FULLNODE_URL; + capabilities: WalletCapabilities = { supportsMultisig: true, supportsTokenScope: true, diff --git a/__tests__/integration/adapters/service.adapter.ts b/__tests__/integration/adapters/service.adapter.ts index fe83e9243..30b992140 100644 --- a/__tests__/integration/adapters/service.adapter.ts +++ b/__tests__/integration/adapters/service.adapter.ts @@ -50,6 +50,8 @@ export class ServiceWalletTestAdapter implements IWalletTestAdapter { defaultPassword = SERVICE_PASSWORD; + originalServerUrl = 'http://localhost:3000/dev/'; + capabilities: WalletCapabilities = { supportsMultisig: false, supportsTokenScope: false, diff --git a/__tests__/integration/adapters/types.ts b/__tests__/integration/adapters/types.ts index 79df5a702..b223bc7a4 100644 --- a/__tests__/integration/adapters/types.ts +++ b/__tests__/integration/adapters/types.ts @@ -95,6 +95,13 @@ export interface IWalletTestAdapter { defaultPinCode: string; defaultPassword: string; + /** + * The server URL that changeServer should revert to after tests. + * Fullnode: the fullnode connection URL (e.g. FULLNODE_URL) + * Wallet Service: the wallet-service base URL (e.g. 'http://localhost:3000/dev/') + */ + originalServerUrl: string; + // --- Lifecycle --- /** One-time setup for the test suite (e.g. start genesis wallet, init configs) */ diff --git a/__tests__/integration/fullnode-specific/server_changes.test.ts b/__tests__/integration/fullnode-specific/server_changes.test.ts new file mode 100644 index 000000000..8afa130e1 --- /dev/null +++ b/__tests__/integration/fullnode-specific/server_changes.test.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * Fullnode-facade changeServer tests. + * + * The fullnode facade's changeServer modifies the connection URL, + * which allows us to verify the change by querying the new server's + * /version endpoint via getVersionData. + * + * Shared changeServer tests live in `shared/server_changes.test.ts`. + */ + +import HathorWallet from '../../../src/new/wallet'; +import { delay } from '../utils/core.util'; +import { GenesisWalletHelper } from '../helpers/genesis-wallet.helper'; +import { FULLNODE_NETWORK_NAME, FULLNODE_URL } from '../configuration/test-constants'; + +describe('[Fullnode] server changes', () => { + let gWallet: HathorWallet; + + beforeAll(async () => { + const { hWallet } = await GenesisWalletHelper.getSingleton(); + gWallet = hWallet; + }); + + afterAll(async () => { + await GenesisWalletHelper.clearListeners(); + await gWallet.stop(); + }); + + it('should change to a different server and revert', async () => { + const testnetUrl = 'https://node1.testnet.hathor.network/v1a/'; + + // Changing from our integration test privatenet to the testnet + await gWallet.changeServer(testnetUrl); + const serverChangeTime = Date.now().valueOf(); + + try { + await delay(100); + + // Validating the server change with getVersionData + const networkData = await gWallet.getVersionData(); + expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); + expect(networkData.network).toMatch(/^testnet.*/); + } finally { + // Always revert to the original server, even if assertions fail + await gWallet.changeServer(FULLNODE_URL); + } + + await delay(100); + + // Verifying the revert to the privatenet + const networkData = await gWallet.getVersionData(); + expect(networkData.timestamp).toBeGreaterThan(serverChangeTime + 200); + expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); + }); +}); diff --git a/__tests__/integration/shared/internal.test.ts b/__tests__/integration/shared/internal.test.ts index 32c632495..c6026bdf7 100644 --- a/__tests__/integration/shared/internal.test.ts +++ b/__tests__/integration/shared/internal.test.ts @@ -95,6 +95,8 @@ describe.each(adapters)('[Shared] internal methods — $name', adapter => { it('getVersionData matches data from a direct fullnode request', async () => { const versionData = await wallet.getVersionData(); + // The raw fullnode /version endpoint returns snake_case keys. + // Both facades transform these to camelCase in getVersionData(). const directResponse = await axios .get('version', { baseURL: FULLNODE_URL, @@ -109,14 +111,26 @@ describe.each(adapters)('[Shared] internal methods — $name', adapter => { }); expect(directResponse.status).toBe(200); - // Both facades should return data consistent with the fullnode. - // Compare only the fields defined in FullNodeVersionData to - // tolerate extra fields the backend may include. + // Map raw snake_case keys to the camelCase keys returned by the facades. + // This allows us to verify both facades faithfully represent the fullnode. + const rawToFacade: Record = { + version: 'version', + network: 'network', + min_weight: 'minWeight', + min_tx_weight: 'minTxWeight', + min_tx_weight_coefficient: 'minTxWeightCoefficient', + min_tx_weight_k: 'minTxWeightK', + token_deposit_percentage: 'tokenDepositPercentage', + reward_spend_min_blocks: 'rewardSpendMinBlocks', + max_number_inputs: 'maxNumberInputs', + max_number_outputs: 'maxNumberOutputs', + }; + const fullnodeData = directResponse.data; - for (const key of Object.keys(baseVersionDataExpectation)) { - expect(versionData).toHaveProperty(key); - expect(fullnodeData).toHaveProperty(key); - expect(versionData[key]).toStrictEqual(fullnodeData[key]); + for (const [rawKey, facadeKey] of Object.entries(rawToFacade)) { + expect(fullnodeData).toHaveProperty(rawKey); + expect(versionData).toHaveProperty(facadeKey); + expect(versionData[facadeKey]).toStrictEqual(fullnodeData[rawKey]); } }); }); diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts index 5d3e2a4af..e1657a6d3 100644 --- a/__tests__/integration/shared/server_changes.test.ts +++ b/__tests__/integration/shared/server_changes.test.ts @@ -5,11 +5,26 @@ * LICENSE file in the root directory of this source tree. */ +/** + * Shared changeServer tests. + * + * changeServer is on IHathorWallet but has different semantics per facade: + * - Fullnode: changes the fullnode connection URL (getServerUrl reflects it) + * - Wallet Service: changes the wallet-service base URL in config + storage + * (getServerUrl still returns the fullnode URL, not the wallet-service URL) + * + * Because the two facades expose changeServer/getServerUrl on different + * endpoints, the only portable assertion is that the wallet remains usable + * after a change+revert cycle. The fullnode-specific test with getVersionData + * validation lives in fullnode-specific/server_changes.test.ts. + * + * Each adapter must provide `originalServerUrl` so the test can revert + * the change regardless of which underlying URL changeServer modifies. + */ + import type { FuzzyWalletType, IWalletTestAdapter } from '../adapters/types'; -import { delay } from '../utils/core.util'; import { FullnodeWalletTestAdapter } from '../adapters/fullnode.adapter'; import { ServiceWalletTestAdapter } from '../adapters/service.adapter'; -import { FULLNODE_NETWORK_NAME, FULLNODE_URL } from '../configuration/test-constants'; const adapters: IWalletTestAdapter[] = [ new FullnodeWalletTestAdapter(), @@ -34,34 +49,16 @@ describe.each(adapters)('[Shared] server changes — $name', adapter => { }); afterAll(async () => { + // Revert to the adapter's original server URL before stopping. + // This is critical because changeServer modifies global config + // that persists across tests. + await wallet.changeServer(adapter.originalServerUrl); await adapter.stopWallet(wallet); }); - it('should change to a different server and revert', async () => { - const testnetUrl = 'https://node1.testnet.hathor.network/v1a/'; - - // Changing from our integration test privatenet to the testnet - await wallet.changeServer(testnetUrl); - const serverChangeTime = Date.now().valueOf(); - - try { - await delay(100); - - // Validating the server change with getVersionData - const networkData = await wallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); - expect(networkData.network).toMatch(/^testnet.*/); - } finally { - // Always revert to the original server, even if assertions fail - await wallet.changeServer(FULLNODE_URL); - } - - await delay(100); - - // Verifying the revert to the privatenet - const networkData = await wallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime + 200); - expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); + it('should accept a new server URL without throwing', async () => { + const newUrl = 'https://node1.testnet.hathor.network/v1a/'; + await expect(wallet.changeServer(newUrl)).resolves.not.toThrow(); }); }); }); From 82bdfeeb2ee80df4fade20572e3a9afb8ec6551d Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 14:10:40 -0300 Subject: [PATCH 07/11] fix: address code review feedback - Use config.getWalletServiceBaseUrl() getter instead of hardcoded URL for originalServerUrl in service adapter - Stop clearing genesis singleton in server_changes to avoid breaking subsequent tests - Replace flaky timing assertion with bounded check - Rethrow network errors instead of swallowing them - Use resolves.toBeUndefined() over resolves.not.toThrow - Guard afterAll cleanup against undefined wallet Co-Authored-By: Claude Opus 4.6 (1M context) --- __tests__/integration/adapters/service.adapter.ts | 5 ++++- .../fullnode-specific/server_changes.test.ts | 7 +++++-- __tests__/integration/shared/internal.test.ts | 2 +- .../integration/shared/server_changes.test.ts | 14 ++++++++------ 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/__tests__/integration/adapters/service.adapter.ts b/__tests__/integration/adapters/service.adapter.ts index 30b992140..2596c64a7 100644 --- a/__tests__/integration/adapters/service.adapter.ts +++ b/__tests__/integration/adapters/service.adapter.ts @@ -7,6 +7,7 @@ */ import { HathorWalletServiceWallet } from '../../../src'; +import config from '../../../src/config'; import { WalletTracker } from '../utils/wallet-tracker.util'; import type Transaction from '../../../src/models/transaction'; import { @@ -50,7 +51,9 @@ export class ServiceWalletTestAdapter implements IWalletTestAdapter { defaultPassword = SERVICE_PASSWORD; - originalServerUrl = 'http://localhost:3000/dev/'; + get originalServerUrl(): string { + return config.getWalletServiceBaseUrl(); + } capabilities: WalletCapabilities = { supportsMultisig: false, diff --git a/__tests__/integration/fullnode-specific/server_changes.test.ts b/__tests__/integration/fullnode-specific/server_changes.test.ts index 8afa130e1..cd55000a3 100644 --- a/__tests__/integration/fullnode-specific/server_changes.test.ts +++ b/__tests__/integration/fullnode-specific/server_changes.test.ts @@ -30,7 +30,8 @@ describe('[Fullnode] server changes', () => { afterAll(async () => { await GenesisWalletHelper.clearListeners(); - await gWallet.stop(); + // Do not stop gWallet: it is the shared singleton from GenesisWalletHelper + // and stopping it would break any subsequent test that calls getSingleton(). }); it('should change to a different server and revert', async () => { @@ -56,7 +57,9 @@ describe('[Fullnode] server changes', () => { // Verifying the revert to the privatenet const networkData = await gWallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime + 200); + const checkTime = Date.now().valueOf(); + expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); + expect(networkData.timestamp).toBeLessThanOrEqual(checkTime + 50); expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); }); }); diff --git a/__tests__/integration/shared/internal.test.ts b/__tests__/integration/shared/internal.test.ts index c6026bdf7..f480b4639 100644 --- a/__tests__/integration/shared/internal.test.ts +++ b/__tests__/integration/shared/internal.test.ts @@ -107,7 +107,7 @@ describe.each(adapters)('[Shared] internal methods — $name', adapter => { if (e.response) { return e.response; } - return {}; + throw e; }); expect(directResponse.status).toBe(200); diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts index e1657a6d3..b2f7b0acd 100644 --- a/__tests__/integration/shared/server_changes.test.ts +++ b/__tests__/integration/shared/server_changes.test.ts @@ -49,16 +49,18 @@ describe.each(adapters)('[Shared] server changes — $name', adapter => { }); afterAll(async () => { - // Revert to the adapter's original server URL before stopping. - // This is critical because changeServer modifies global config - // that persists across tests. - await wallet.changeServer(adapter.originalServerUrl); - await adapter.stopWallet(wallet); + if (wallet) { + // Revert to the adapter's original server URL before stopping. + // This is critical because changeServer modifies global config + // that persists across tests. + await wallet.changeServer(adapter.originalServerUrl); + await adapter.stopWallet(wallet); + } }); it('should accept a new server URL without throwing', async () => { const newUrl = 'https://node1.testnet.hathor.network/v1a/'; - await expect(wallet.changeServer(newUrl)).resolves.not.toThrow(); + await expect(wallet.changeServer(newUrl)).resolves.toBeUndefined(); }); }); }); From e0e11accd962bc91c5d77f293aa06945234fba59 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Thu, 26 Mar 2026 16:58:39 -0300 Subject: [PATCH 08/11] fix: protects originalServerUrl Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- __tests__/integration/adapters/service.adapter.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/__tests__/integration/adapters/service.adapter.ts b/__tests__/integration/adapters/service.adapter.ts index 2596c64a7..8a0d5ff4b 100644 --- a/__tests__/integration/adapters/service.adapter.ts +++ b/__tests__/integration/adapters/service.adapter.ts @@ -51,8 +51,19 @@ export class ServiceWalletTestAdapter implements IWalletTestAdapter { defaultPassword = SERVICE_PASSWORD; + private _originalServerUrl?: string; + get originalServerUrl(): string { - return config.getWalletServiceBaseUrl(); + if (!this._originalServerUrl) { + throw new Error('originalServerUrl not initialized. Call suiteSetup() first.'); + } + return this._originalServerUrl; + } + + async suiteSetup(): Promise { + initializeServiceGlobalConfigs(); + this._originalServerUrl = config.getWalletServiceBaseUrl(); + await GenesisWalletServiceHelper.start(); } capabilities: WalletCapabilities = { From 3f6fcb18d1b974c2ba6afbcc70c297595b91c3cb Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Fri, 27 Mar 2026 09:17:39 -0300 Subject: [PATCH 09/11] fix: originalServerUrl issues --- __tests__/integration/adapters/service.adapter.ts | 5 ----- __tests__/integration/shared/server_changes.test.ts | 9 +++++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/integration/adapters/service.adapter.ts b/__tests__/integration/adapters/service.adapter.ts index 8a0d5ff4b..aac49c498 100644 --- a/__tests__/integration/adapters/service.adapter.ts +++ b/__tests__/integration/adapters/service.adapter.ts @@ -96,11 +96,6 @@ export class ServiceWalletTestAdapter implements IWalletTestAdapter { return wallet as unknown as HathorWalletServiceWallet; } - async suiteSetup(): Promise { - initializeServiceGlobalConfigs(); - await GenesisWalletServiceHelper.start(); - } - async suiteTeardown(): Promise { await this.stopAllWallets(); await GenesisWalletServiceHelper.stop(); diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts index b2f7b0acd..34025ad52 100644 --- a/__tests__/integration/shared/server_changes.test.ts +++ b/__tests__/integration/shared/server_changes.test.ts @@ -32,8 +32,13 @@ const adapters: IWalletTestAdapter[] = [ ]; describe.each(adapters)('[Shared] server changes — $name', adapter => { + // Captured after suiteSetup (config initialized) but before any test + // mutates it via changeServer. Safe to use in afterAll for revert. + let serverUrlBeforeTests: string; + beforeAll(async () => { await adapter.suiteSetup(); + serverUrlBeforeTests = adapter.originalServerUrl; }); afterAll(async () => { @@ -50,10 +55,10 @@ describe.each(adapters)('[Shared] server changes — $name', adapter => { afterAll(async () => { if (wallet) { - // Revert to the adapter's original server URL before stopping. + // Revert to the server URL captured before tests ran. // This is critical because changeServer modifies global config // that persists across tests. - await wallet.changeServer(adapter.originalServerUrl); + await wallet.changeServer(serverUrlBeforeTests); await adapter.stopWallet(wallet); } }); From 5181f23644b0fc2726c305638c7b8973f4595574 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Mon, 30 Mar 2026 13:29:52 -0300 Subject: [PATCH 10/11] fix: rename misleading test, remove clearListeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename 'should reload the storage' to 'should call processHistory when connection state changes to CONNECTED' to reflect what the test actually verifies. Remove clearListeners() calls from afterAll hooks in our new files. No test code registers 'new-tx' listeners on the genesis wallet — clearListeners strips the wallet's own internal handler instead. Co-Authored-By: Claude Opus 4.6 (1M context) --- __tests__/integration/fullnode-specific/internal.test.ts | 4 +--- .../integration/fullnode-specific/server_changes.test.ts | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/__tests__/integration/fullnode-specific/internal.test.ts b/__tests__/integration/fullnode-specific/internal.test.ts index caefbd3f3..280a1f4b3 100644 --- a/__tests__/integration/fullnode-specific/internal.test.ts +++ b/__tests__/integration/fullnode-specific/internal.test.ts @@ -33,8 +33,6 @@ describe('[Fullnode] internal methods', () => { afterAll(async () => { await hWallet.stop(); - await GenesisWalletHelper.clearListeners(); - await gWallet.stop(); }); it('should test the debug methods', async () => { @@ -47,7 +45,7 @@ describe('[Fullnode] internal methods', () => { expect(gWallet.debug).toStrictEqual(false); }); - it('should reload the storage', async () => { + it('should call processHistory when connection state changes to CONNECTED', async () => { await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10n); const spy = jest.spyOn(hWallet.storage, 'processHistory'); // Simulate that we received an event of the connection becoming active diff --git a/__tests__/integration/fullnode-specific/server_changes.test.ts b/__tests__/integration/fullnode-specific/server_changes.test.ts index cd55000a3..1fba714d7 100644 --- a/__tests__/integration/fullnode-specific/server_changes.test.ts +++ b/__tests__/integration/fullnode-specific/server_changes.test.ts @@ -29,7 +29,6 @@ describe('[Fullnode] server changes', () => { }); afterAll(async () => { - await GenesisWalletHelper.clearListeners(); // Do not stop gWallet: it is the shared singleton from GenesisWalletHelper // and stopping it would break any subsequent test that calls getSingleton(). }); From 7a17deea6be05e8d257828866ba33ff4f55cd150 Mon Sep 17 00:00:00 2001 From: Tulio Miranda Date: Mon, 30 Mar 2026 13:55:24 -0300 Subject: [PATCH 11/11] test: strengthen changeServer tests for both facades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add testnetServerUrl to IWalletTestAdapter so the shared test can validate changeServer via getVersionData against real testnet endpoints on both facades. Replace the weak resolves.toBeUndefined assertion with a full change→validate→revert→validate cycle that proves the server change actually took effect. Remove fullnode-specific/server_changes.test.ts which is now covered by the shared test. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../integration/adapters/fullnode.adapter.ts | 2 + .../integration/adapters/service.adapter.ts | 2 + __tests__/integration/adapters/types.ts | 7 ++ .../fullnode-specific/server_changes.test.ts | 64 ------------------- .../integration/shared/server_changes.test.ts | 46 ++++++++----- 5 files changed, 40 insertions(+), 81 deletions(-) delete mode 100644 __tests__/integration/fullnode-specific/server_changes.test.ts diff --git a/__tests__/integration/adapters/fullnode.adapter.ts b/__tests__/integration/adapters/fullnode.adapter.ts index d7870f9af..ca7dba5e7 100644 --- a/__tests__/integration/adapters/fullnode.adapter.ts +++ b/__tests__/integration/adapters/fullnode.adapter.ts @@ -53,6 +53,8 @@ export class FullnodeWalletTestAdapter implements IWalletTestAdapter { originalServerUrl = FULLNODE_URL; + testnetServerUrl = 'https://node1.testnet.hathor.network/v1a/'; + capabilities: WalletCapabilities = { supportsMultisig: true, supportsTokenScope: true, diff --git a/__tests__/integration/adapters/service.adapter.ts b/__tests__/integration/adapters/service.adapter.ts index aac49c498..05cc7941a 100644 --- a/__tests__/integration/adapters/service.adapter.ts +++ b/__tests__/integration/adapters/service.adapter.ts @@ -53,6 +53,8 @@ export class ServiceWalletTestAdapter implements IWalletTestAdapter { private _originalServerUrl?: string; + testnetServerUrl = 'https://wallet-service.testnet.hathor.network/'; + get originalServerUrl(): string { if (!this._originalServerUrl) { throw new Error('originalServerUrl not initialized. Call suiteSetup() first.'); diff --git a/__tests__/integration/adapters/types.ts b/__tests__/integration/adapters/types.ts index b223bc7a4..ae0a9137b 100644 --- a/__tests__/integration/adapters/types.ts +++ b/__tests__/integration/adapters/types.ts @@ -102,6 +102,13 @@ export interface IWalletTestAdapter { */ originalServerUrl: string; + /** + * A real testnet server URL for validating changeServer via getVersionData. + * Fullnode: a testnet fullnode URL + * Wallet Service: a testnet wallet-service URL + */ + testnetServerUrl: string; + // --- Lifecycle --- /** One-time setup for the test suite (e.g. start genesis wallet, init configs) */ diff --git a/__tests__/integration/fullnode-specific/server_changes.test.ts b/__tests__/integration/fullnode-specific/server_changes.test.ts deleted file mode 100644 index 1fba714d7..000000000 --- a/__tests__/integration/fullnode-specific/server_changes.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) Hathor Labs and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Fullnode-facade changeServer tests. - * - * The fullnode facade's changeServer modifies the connection URL, - * which allows us to verify the change by querying the new server's - * /version endpoint via getVersionData. - * - * Shared changeServer tests live in `shared/server_changes.test.ts`. - */ - -import HathorWallet from '../../../src/new/wallet'; -import { delay } from '../utils/core.util'; -import { GenesisWalletHelper } from '../helpers/genesis-wallet.helper'; -import { FULLNODE_NETWORK_NAME, FULLNODE_URL } from '../configuration/test-constants'; - -describe('[Fullnode] server changes', () => { - let gWallet: HathorWallet; - - beforeAll(async () => { - const { hWallet } = await GenesisWalletHelper.getSingleton(); - gWallet = hWallet; - }); - - afterAll(async () => { - // Do not stop gWallet: it is the shared singleton from GenesisWalletHelper - // and stopping it would break any subsequent test that calls getSingleton(). - }); - - it('should change to a different server and revert', async () => { - const testnetUrl = 'https://node1.testnet.hathor.network/v1a/'; - - // Changing from our integration test privatenet to the testnet - await gWallet.changeServer(testnetUrl); - const serverChangeTime = Date.now().valueOf(); - - try { - await delay(100); - - // Validating the server change with getVersionData - const networkData = await gWallet.getVersionData(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); - expect(networkData.network).toMatch(/^testnet.*/); - } finally { - // Always revert to the original server, even if assertions fail - await gWallet.changeServer(FULLNODE_URL); - } - - await delay(100); - - // Verifying the revert to the privatenet - const networkData = await gWallet.getVersionData(); - const checkTime = Date.now().valueOf(); - expect(networkData.timestamp).toBeGreaterThan(serverChangeTime); - expect(networkData.timestamp).toBeLessThanOrEqual(checkTime + 50); - expect(networkData.network).toStrictEqual(FULLNODE_NETWORK_NAME); - }); -}); diff --git a/__tests__/integration/shared/server_changes.test.ts b/__tests__/integration/shared/server_changes.test.ts index 34025ad52..1a3d3dfad 100644 --- a/__tests__/integration/shared/server_changes.test.ts +++ b/__tests__/integration/shared/server_changes.test.ts @@ -8,23 +8,23 @@ /** * Shared changeServer tests. * - * changeServer is on IHathorWallet but has different semantics per facade: - * - Fullnode: changes the fullnode connection URL (getServerUrl reflects it) - * - Wallet Service: changes the wallet-service base URL in config + storage - * (getServerUrl still returns the fullnode URL, not the wallet-service URL) + * Both facades implement changeServer on IHathorWallet. Although the + * underlying URL they modify differs (fullnode connection URL vs. + * wallet-service base URL), getVersionData() is the universal + * observable side-effect: it routes through whichever URL changeServer + * modifies, returning FullNodeVersionData with a `network` field that + * distinguishes testnet from privatenet. * - * Because the two facades expose changeServer/getServerUrl on different - * endpoints, the only portable assertion is that the wallet remains usable - * after a change+revert cycle. The fullnode-specific test with getVersionData - * validation lives in fullnode-specific/server_changes.test.ts. - * - * Each adapter must provide `originalServerUrl` so the test can revert - * the change regardless of which underlying URL changeServer modifies. + * Each adapter provides: + * - `originalServerUrl`: the URL to revert to after tests + * - `testnetServerUrl`: a real testnet endpoint for validation */ import type { FuzzyWalletType, IWalletTestAdapter } from '../adapters/types'; +import { delay } from '../utils/core.util'; import { FullnodeWalletTestAdapter } from '../adapters/fullnode.adapter'; import { ServiceWalletTestAdapter } from '../adapters/service.adapter'; +import { FULLNODE_NETWORK_NAME } from '../configuration/test-constants'; const adapters: IWalletTestAdapter[] = [ new FullnodeWalletTestAdapter(), @@ -55,17 +55,29 @@ describe.each(adapters)('[Shared] server changes — $name', adapter => { afterAll(async () => { if (wallet) { - // Revert to the server URL captured before tests ran. - // This is critical because changeServer modifies global config - // that persists across tests. await wallet.changeServer(serverUrlBeforeTests); await adapter.stopWallet(wallet); } }); - it('should accept a new server URL without throwing', async () => { - const newUrl = 'https://node1.testnet.hathor.network/v1a/'; - await expect(wallet.changeServer(newUrl)).resolves.toBeUndefined(); + it('should change to a testnet server and verify via getVersionData', async () => { + await wallet.changeServer(adapter.testnetServerUrl); + + try { + await delay(100); + + const testnetData = await wallet.getVersionData(); + expect(testnetData.network).toMatch(/^testnet.*/); + } finally { + // Always revert, even if assertions fail + await wallet.changeServer(serverUrlBeforeTests); + } + + await delay(100); + + // Verify the revert to the privatenet + const revertedData = await wallet.getVersionData(); + expect(revertedData.network).toStrictEqual(FULLNODE_NETWORK_NAME); }); }); });