-
Notifications
You must be signed in to change notification settings - Fork 0
test: add integration test for generateWallet #212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pranishnepal
wants to merge
1
commit into
master
Choose a base branch
from
WAL-1502
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "id": "backup-key-id", | ||
| "pub": "xpub_backup", | ||
| "keyType": "independent", | ||
| "source": "backup", | ||
| "type": "independent" | ||
| } |
10 changes: 10 additions & 0 deletions
10
src/__tests__/integration/fixtures/bitgo/addKey.bitgo.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "id": "bitgo-key-id", | ||
| "pub": "xpub_bitgo", | ||
| "keyType": "independent", | ||
| "source": "bitgo", | ||
| "type": "independent", | ||
| "isBitGo": true, | ||
| "isTrust": false, | ||
| "hsmType": "institutional" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "id": "user-key-id", | ||
| "pub": "xpub_user", | ||
| "keyType": "independent", | ||
| "source": "user", | ||
| "type": "independent" | ||
| } |
42 changes: 42 additions & 0 deletions
42
src/__tests__/integration/fixtures/bitgo/createWallet.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "id": "test-wallet-id", | ||
| "coin": "tbtc", | ||
| "label": "test-wallet", | ||
| "m": 2, | ||
| "n": 3, | ||
| "keys": ["user-key-id", "backup-key-id", "bitgo-key-id"], | ||
| "keySignatures": {}, | ||
| "type": "advanced", | ||
| "multisigType": "onchain", | ||
| "isCold": true, | ||
| "enterprise": "test-enterprise", | ||
| "organization": "test-org", | ||
| "bitgoOrg": "BitGo Inc", | ||
| "tags": ["test-wallet-id", "test-enterprise"], | ||
| "disableTransactionNotifications": false, | ||
| "freeze": {}, | ||
| "deleted": false, | ||
| "approvalsRequired": 1, | ||
| "coinSpecific": {}, | ||
| "admin": {}, | ||
| "allowBackupKeySigning": false, | ||
| "clientFlags": [], | ||
| "recoverable": false, | ||
| "startDate": "2025-01-01T00:00:00.000Z", | ||
| "hasLargeNumberOfAddresses": false, | ||
| "config": {}, | ||
| "balanceString": "0", | ||
| "confirmedBalanceString": "0", | ||
| "spendableBalanceString": "0", | ||
| "pendingApprovals": [], | ||
| "receiveAddress": { | ||
| "id": "addr-id", | ||
| "address": "0xexampleaddress", | ||
| "chain": 0, | ||
| "index": 0, | ||
| "coin": "tbtc", | ||
| "wallet": "test-wallet-id", | ||
| "coinSpecific": {} | ||
| }, | ||
| "users": [{ "user": "user-id", "permissions": ["admin", "spend", "view"] }] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import 'should'; | ||
| import { startServices, IntegServices } from './helpers/setup'; | ||
| import { LOCALHOST } from './helpers/servers'; | ||
| import { SigningMode } from '../../shared/types'; | ||
| import type { GenerateWalletResponseBody } from '../../masterBitgoExpress/routers/generateWalletRoute'; | ||
|
|
||
| describe('Generate wallet: LOCAL signing', () => { | ||
| let services: IntegServices; | ||
|
|
||
| before(async () => { | ||
| services = await startServices(); | ||
| }); | ||
|
|
||
| after(async () => { | ||
| await services.teardown(); | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| services.keyProvider.calls.length = 0; | ||
| services.bitgo.calls.length = 0; | ||
| }); | ||
|
|
||
| it('generates a tbtc onchain wallet end-to-end', async () => { | ||
| const res = await fetch( | ||
| `http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/generate`, | ||
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' }, | ||
| body: JSON.stringify({ | ||
| enterprise: 'test-enterprise', | ||
| label: 'test-wallet', | ||
| multisigType: 'onchain', | ||
| }), | ||
| }, | ||
| ); | ||
|
|
||
| res.status.should.equal(200); | ||
| const body = (await res.json()) as GenerateWalletResponseBody; | ||
| body.should.have.property('wallet'); | ||
| body.wallet.should.have.property('id', 'test-wallet-id'); | ||
|
|
||
| /** In local mode, AWM stores keys via POST /key — POST /key/generate must NOT be called */ | ||
| const keyProviderStoreCalls = services.keyProvider.calls.filter((c) => c.path === '/key'); | ||
| keyProviderStoreCalls.should.have.length(2); | ||
|
|
||
| const keyProviderGenerateCalls = services.keyProvider.calls.filter( | ||
| (c) => c.path === '/key/generate', | ||
| ); | ||
| keyProviderGenerateCalls.should.have.length(0); | ||
|
|
||
| /** BitGo received 3 keychain adds */ | ||
| const bitgoKeyCalls = services.bitgo.calls.filter( | ||
| (c) => c.method === 'POST' && c.path.endsWith('/key'), | ||
| ); | ||
| bitgoKeyCalls.should.have.length(3); | ||
|
|
||
| /** and 1 wallet add */ | ||
| const walletAddCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/wallet/add')); | ||
| walletAddCalls.should.have.length(1); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Generate wallet: EXTERNAL signing', () => { | ||
| let services: IntegServices; | ||
|
|
||
| before(async () => { | ||
| services = await startServices({ signingMode: SigningMode.EXTERNAL }); | ||
| }); | ||
|
|
||
| after(async () => { | ||
| await services.teardown(); | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| services.keyProvider.calls.length = 0; | ||
| services.bitgo.calls.length = 0; | ||
| }); | ||
|
|
||
| it('generates a tbtc onchain wallet — key provider generates keys (external signing mode)', async () => { | ||
| const res = await fetch( | ||
| `http://${LOCALHOST}:${services.mbePort}/api/v1/tbtc/advancedwallet/generate`, | ||
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-token' }, | ||
| body: JSON.stringify({ | ||
| enterprise: 'test-enterprise', | ||
| label: 'test-wallet', | ||
| multisigType: 'onchain', | ||
| }), | ||
| }, | ||
| ); | ||
|
|
||
| res.status.should.equal(200); | ||
| const body = (await res.json()) as GenerateWalletResponseBody; | ||
| body.should.have.property('wallet'); | ||
| body.wallet.should.have.property('id', 'test-wallet-id'); | ||
|
|
||
| /** | ||
| * In external mode, AWM delegates key generation to the key provider. | ||
| */ | ||
| const keyProviderGenerateCalls = services.keyProvider.calls.filter( | ||
| (c) => c.path === '/key/generate', | ||
| ); | ||
| keyProviderGenerateCalls.should.have.length(2); | ||
|
|
||
| /** POST /key should NOT be called — AWM never generates keys locally in external mode */ | ||
| const keyProviderStoreCalls = services.keyProvider.calls.filter((c) => c.path === '/key'); | ||
| keyProviderStoreCalls.should.have.length(0); | ||
|
|
||
| /** BitGo receives 3 keychain adds */ | ||
| const bitgoKeyCalls = services.bitgo.calls.filter( | ||
| (c) => c.method === 'POST' && c.path.endsWith('/key'), | ||
| ); | ||
| bitgoKeyCalls.should.have.length(3); | ||
|
|
||
| /** and 1 wallet add */ | ||
| const walletAddCalls = services.bitgo.calls.filter((c) => c.path.endsWith('/wallet/add')); | ||
| walletAddCalls.should.have.length(1); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import * as http from 'http'; | ||
| import * as path from 'path'; | ||
| import express from 'express'; | ||
| import { listen, close } from './servers'; | ||
|
|
||
| export interface MockBitgoCall { | ||
| method: string; | ||
| path: string; | ||
| body: unknown; | ||
| } | ||
|
|
||
| export interface MockBitgoServer { | ||
| port: number; | ||
| calls: MockBitgoCall[]; | ||
| close(): Promise<void>; | ||
| } | ||
|
|
||
| const FIXTURES_DIR = path.resolve(__dirname, '../fixtures/bitgo'); | ||
|
|
||
| function loadFixture(name: string): unknown { | ||
| return require(`${FIXTURES_DIR}/${name}.json`); | ||
| } | ||
|
|
||
| export async function startMockBitgoServer(): Promise<MockBitgoServer> { | ||
| const calls: MockBitgoCall[] = []; | ||
|
|
||
| const app = express(); | ||
| app.use(express.json()); | ||
|
|
||
| app.use((req, _res, next) => { | ||
| calls.push({ method: req.method, path: req.path, body: req.body }); | ||
| next(); | ||
| }); | ||
|
|
||
| /** SDK calls this on every BitGo instance initialisation */ | ||
| app.get('/api/v1/client/constants', (_req, res) => { | ||
| res.json({ ttl: 3600, constants: {} }); | ||
| }); | ||
|
|
||
| /** Add keychain — source distinguishes user / backup / bitgo */ | ||
| app.post('/api/v2/:coin/key', (req, res) => { | ||
| const { coin } = req.params; | ||
| const source = req.body?.source; | ||
| const fixtureName = | ||
| source === 'user' ? 'addKey.user' : source === 'backup' ? 'addKey.backup' : 'addKey.bitgo'; | ||
| const fixture = loadFixture(fixtureName) as Record<string, unknown>; | ||
| return res.json({ ...fixture, coin }); | ||
| }); | ||
|
|
||
| /** Create wallet */ | ||
| app.post('/api/v2/:coin/wallet/add', (req, res) => { | ||
| const { coin } = req.params; | ||
| const fixture = loadFixture('createWallet') as Record<string, unknown>; | ||
| res.json({ ...fixture, coin }); | ||
| }); | ||
|
|
||
| const server = http.createServer(app); | ||
| const port = await listen(server); | ||
|
|
||
| return { port, calls, close: () => close(server) }; | ||
| } |
74 changes: 74 additions & 0 deletions
74
src/__tests__/integration/helpers/mockKeyProviderServer.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import * as http from 'http'; | ||
| import express from 'express'; | ||
| import { BitGoAPI } from '@bitgo-beta/sdk-api'; | ||
| import { Hteth } from '@bitgo-beta/sdk-coin-eth'; | ||
| import { Tbtc } from '@bitgo-beta/sdk-coin-btc'; | ||
| import { listen, close } from './servers'; | ||
|
|
||
| export interface MockKeyProviderCall { | ||
| method: string; | ||
| path: string; | ||
| body: unknown; | ||
| } | ||
|
|
||
| export interface MockKeyProviderServer { | ||
| port: number; | ||
| calls: MockKeyProviderCall[]; | ||
| close(): Promise<void>; | ||
| } | ||
|
|
||
| interface StoredKey { | ||
| prv: string; | ||
| source: string; | ||
| type: string; | ||
| } | ||
|
|
||
| /** | ||
| * @returns BitGo Instance with coins registered | ||
| */ | ||
| function createBitgoInstance(): BitGoAPI { | ||
| const instance = new BitGoAPI({ env: 'test' }); | ||
| instance.register('hteth', Hteth.createInstance); | ||
| instance.register('tbtc', Tbtc.createInstance); | ||
| return instance; | ||
| } | ||
|
|
||
| function generateKeypair(coin: string): { pub: string; prv: string } { | ||
| const keychain = createBitgoInstance().coin(coin).keychains().create(); | ||
| if (!keychain.pub || !keychain.prv) | ||
| throw new Error(`Failed to generate keypair for coin: ${coin}`); | ||
| return { pub: keychain.pub, prv: keychain.prv }; | ||
| } | ||
|
|
||
| export async function startMockKeyProviderServer(): Promise<MockKeyProviderServer> { | ||
| const calls: MockKeyProviderCall[] = []; | ||
| const keyStore = new Map<string, StoredKey>(); | ||
|
|
||
| const app = express(); | ||
| app.use(express.json()); | ||
|
|
||
| app.use((req, _res, next) => { | ||
| calls.push({ method: req.method, path: req.path, body: req.body }); | ||
| next(); | ||
| }); | ||
|
|
||
| /** External signing mode — key provider generates the key */ | ||
| app.post('/key/generate', (req, res) => { | ||
| const { coin, source, type } = req.body; | ||
| const { pub, prv } = generateKeypair(coin); | ||
| keyStore.set(pub, { prv, source, type }); | ||
| res.json({ pub, coin, source, type }); | ||
| }); | ||
|
|
||
| /** Local signing mode — AWM generates the key and sends it here for storage */ | ||
| app.post('/key', (req, res) => { | ||
| const { pub, prv, coin, source, type } = req.body; | ||
| keyStore.set(pub, { prv, source, type }); | ||
| res.json({ pub, coin, source, type }); | ||
| }); | ||
|
|
||
| const server = http.createServer(app); | ||
| const port = await listen(server); | ||
|
|
||
| return { port, calls, close: () => close(server) }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0 here means use whatever port is available!