@@ -4,6 +4,7 @@ import { ProxyAgent } from 'proxy-agent';
44import * as sinon from 'sinon' ;
55import nock from 'nock' ;
66import type { IHmacAuthStrategy } from '@bitgo/sdk-hmac' ;
7+ import { GlobalCoinFactory , CoinConstructor } from '@bitgo/sdk-core' ;
78
89describe ( 'Constructor' , function ( ) {
910 describe ( 'cookiesPropagationEnabled argument' , function ( ) {
@@ -794,4 +795,79 @@ describe('Constructor', function () {
794795 nock . cleanAll ( ) ;
795796 } ) ;
796797 } ) ;
798+
799+ describe ( 'registerToken' , function ( ) {
800+ let bitgo : BitGoAPI ;
801+ let sandbox : sinon . SinonSandbox ;
802+
803+ beforeEach ( function ( ) {
804+ bitgo = new BitGoAPI ( { env : 'custom' , customRootURI : 'https://app.example.local' } ) ;
805+ sandbox = sinon . createSandbox ( ) ;
806+ } ) ;
807+
808+ afterEach ( function ( ) {
809+ sandbox . restore ( ) ;
810+ } ) ;
811+
812+ it ( 'should call GlobalCoinFactory.registerToken and allow sdk.coin() to resolve the registered token' , function ( ) {
813+ const mockCoinInstance = { type : 'test-ams-dynamic-token' } as any ;
814+ const mockConstructor = sandbox . stub ( ) . returns ( mockCoinInstance ) as unknown as CoinConstructor ;
815+ const mockCoinConfig = { name : 'test-ams-dynamic-token' , id : 'test-ams-dynamic-token-id' } as any ;
816+
817+ sandbox . stub ( GlobalCoinFactory , 'registerToken' ) ;
818+ sandbox . stub ( GlobalCoinFactory , 'getInstance' ) . callsFake ( ( _bitgo , name ) => {
819+ if ( name === 'test-ams-dynamic-token' ) {
820+ return mockConstructor ( _bitgo , mockCoinConfig ) ;
821+ }
822+ throw new Error ( `Unsupported coin: ${ name } ` ) ;
823+ } ) ;
824+
825+ bitgo . registerToken ( mockCoinConfig , mockConstructor ) ;
826+
827+ const coin = bitgo . coin ( 'test-ams-dynamic-token' ) ;
828+ coin . should . equal ( mockCoinInstance ) ;
829+ ( GlobalCoinFactory . registerToken as sinon . SinonStub )
830+ . calledOnceWith ( mockCoinConfig , mockConstructor )
831+ . should . be . true ( ) ;
832+ } ) ;
833+
834+ it ( 'should be idempotent — calling registerToken twice for same token does not throw' , function ( ) {
835+ const mockCoinConfig = { name : 'test-ams-idempotent-token' , id : 'test-ams-idempotent-token-id' } as any ;
836+ const mockConstructor = sandbox . stub ( ) as unknown as CoinConstructor ;
837+ sandbox . stub ( GlobalCoinFactory , 'registerToken' ) ;
838+
839+ ( ( ) => {
840+ bitgo . registerToken ( mockCoinConfig , mockConstructor ) ;
841+ bitgo . registerToken ( mockCoinConfig , mockConstructor ) ;
842+ } ) . should . not . throw ( ) ;
843+
844+ ( GlobalCoinFactory . registerToken as sinon . SinonStub ) . callCount . should . equal ( 2 ) ;
845+ } ) ;
846+
847+ it ( 'should not affect existing statics coins after registerToken calls' , function ( ) {
848+ const newCoinConfig = { name : 'test-ams-new-token' , id : 'test-ams-new-token-id' } as any ;
849+ const newConstructor = sandbox . stub ( ) as unknown as CoinConstructor ;
850+ const existingCoinInstance = { type : 'eth' } as any ;
851+ const existingConstructor = sandbox . stub ( ) . returns ( existingCoinInstance ) as unknown as CoinConstructor ;
852+
853+ const registerTokenStub = sandbox . stub ( GlobalCoinFactory , 'registerToken' ) ;
854+ sandbox . stub ( GlobalCoinFactory , 'getInstance' ) . callsFake ( ( _bitgo , name ) => {
855+ if ( name === 'eth' ) {
856+ return existingConstructor ( _bitgo , undefined ) ;
857+ }
858+ throw new Error ( `Unsupported coin: ${ name } ` ) ;
859+ } ) ;
860+
861+ // Register a new AMS token
862+ bitgo . registerToken ( newCoinConfig , newConstructor ) ;
863+
864+ // Existing statics coin should still resolve correctly
865+ const ethCoin = bitgo . coin ( 'eth' ) ;
866+ ethCoin . should . equal ( existingCoinInstance ) ;
867+
868+ // registerToken was called only once (for the new token, not for eth)
869+ registerTokenStub . calledOnce . should . be . true ( ) ;
870+ registerTokenStub . calledWith ( newCoinConfig , newConstructor ) . should . be . true ( ) ;
871+ } ) ;
872+ } ) ;
797873} ) ;
0 commit comments