@@ -15,7 +15,14 @@ import {
1515 Params ,
1616 prepareInitiateCorporateAction ,
1717} from '~/api/procedures/initiateCorporateAction' ;
18- import { Checkpoint , CheckpointSchedule , Context , CorporateAction , Procedure } from '~/internal' ;
18+ import {
19+ Checkpoint ,
20+ CheckpointSchedule ,
21+ Context ,
22+ CorporateAction ,
23+ Identity ,
24+ Procedure ,
25+ } from '~/internal' ;
1926import { dsMockUtils , entityMockUtils , procedureMockUtils } from '~/testUtils/mocks' ;
2027import { Mocked } from '~/testUtils/types' ;
2128import {
@@ -45,7 +52,7 @@ describe('assertCheckpointValue', () => {
4552 } ) ;
4653
4754 it ( 'should throw an error if the provided Date is in the past' , ( ) => {
48- const checkpoint = new Date ( new Date ( ) . getTime ( ) - 1000 * 60 * 60 * 24 ) ;
55+ const checkpoint = new Date ( Date . now ( ) - 1000 * 60 * 60 * 24 ) ;
4956
5057 return expect ( assertCheckpointValue ( checkpoint ) ) . rejects . toThrow (
5158 'Checkpoint must be in the future'
@@ -56,7 +63,7 @@ describe('assertCheckpointValue', () => {
5663 const mockCheckpoint = new Checkpoint ( { id : new BigNumber ( 1 ) , assetId : asset . id } , context ) ;
5764 mockCheckpoint . createdAt = jest
5865 . fn ( )
59- . mockResolvedValue ( new Date ( new Date ( ) . getTime ( ) - 1000 * 60 * 60 * 24 ) ) ;
66+ . mockResolvedValue ( new Date ( Date . now ( ) - 1000 * 60 * 60 * 24 ) ) ;
6067
6168 return expect ( assertCheckpointValue ( mockCheckpoint ) ) . rejects . toThrow (
6269 'Checkpoint must be in the future'
@@ -89,6 +96,9 @@ describe('initiateCorporateAction procedure', () => {
8996 let initiateCorporateActionTransaction : PolymeshTx < unknown [ ] > ;
9097
9198 let corporateActionParamsToMeshCorporateActionArgsSpy : jest . SpyInstance ;
99+ let stringToIdentityIdSpy : jest . SpyInstance ;
100+ let assetToMeshAssetIdSpy : jest . SpyInstance ;
101+ let signingIdentity : Identity ;
92102
93103 beforeAll ( ( ) => {
94104 entityMockUtils . initMocks ( ) ;
@@ -97,10 +107,10 @@ describe('initiateCorporateAction procedure', () => {
97107
98108 assetId = '0x12341234123412341234123412341234' ;
99109 asset = entityMockUtils . getFungibleAssetInstance ( { assetId } ) ;
100- checkpoint = new Date ( new Date ( ) . getTime ( ) + 1000 * 60 * 60 * 24 ) ;
110+ checkpoint = new Date ( Date . now ( ) + 1000 * 60 * 60 * 24 ) ;
101111 kind = CorporateActionKind . IssuerNotice ;
102112
103- declarationDate = new Date ( new Date ( ) . getTime ( ) - 1000 * 60 * 60 * 24 ) ;
113+ declarationDate = new Date ( Date . now ( ) - 1000 * 60 * 60 * 24 ) ;
104114 description = 'someDescription' ;
105115 rawCorporateActionArgs = dsMockUtils . createMockInitiateCorporateActionArgs ( {
106116 assetId,
@@ -117,6 +127,10 @@ describe('initiateCorporateAction procedure', () => {
117127 utilsConversionModule ,
118128 'corporateActionParamsToMeshCorporateActionArgs'
119129 ) ;
130+ stringToIdentityIdSpy = jest . spyOn ( utilsConversionModule , 'stringToIdentityId' ) ;
131+ assetToMeshAssetIdSpy = jest . spyOn ( utilsConversionModule , 'assetToMeshAssetId' ) ;
132+
133+ signingIdentity = entityMockUtils . getIdentityInstance ( { did : 'someDid' } ) ;
120134 } ) ;
121135
122136 beforeEach ( ( ) => {
@@ -126,6 +140,20 @@ describe('initiateCorporateAction procedure', () => {
126140 ) ;
127141
128142 mockContext = dsMockUtils . getContextInstance ( ) ;
143+ mockContext . getSigningIdentity = jest . fn ( ) . mockResolvedValue ( signingIdentity ) ;
144+
145+ const rawAssetId = dsMockUtils . createMockAssetId ( assetId ) ;
146+ const rawIdentityId = dsMockUtils . createMockIdentityId ( signingIdentity . did ) ;
147+
148+ when ( assetToMeshAssetIdSpy ) . calledWith ( asset , mockContext ) . mockReturnValue ( rawAssetId ) ;
149+ when ( stringToIdentityIdSpy )
150+ . calledWith ( signingIdentity . did , mockContext )
151+ . mockReturnValue ( rawIdentityId ) ;
152+
153+ // Default: mock Full agent (asset owner scenario)
154+ dsMockUtils . createQueryMock ( 'externalAgents' , 'groupOfAgent' , {
155+ returnValue : dsMockUtils . createMockOption ( dsMockUtils . createMockAgentGroup ( 'Full' ) ) ,
156+ } ) ;
129157
130158 when ( corporateActionParamsToMeshCorporateActionArgsSpy )
131159 . calledWith (
@@ -213,6 +241,81 @@ describe('initiateCorporateAction procedure', () => {
213241 } ) ;
214242 } ) ;
215243
244+ describe ( 'tax withholdings validation' , ( ) => {
245+ it ( 'should validate tax withholdings and detect duplicates' , async ( ) => {
246+ const proc = procedureMockUtils . getInstance < Params , CorporateAction > ( mockContext ) ;
247+
248+ // Set maxDidWhts to at least 2 to allow duplicate check to run before limit check
249+ dsMockUtils . setConstMock ( 'corporateAction' , 'maxDidWhts' , {
250+ returnValue : dsMockUtils . createMockU32 ( new BigNumber ( 10 ) ) ,
251+ } ) ;
252+
253+ const identity1 = entityMockUtils . getIdentityInstance ( { did : 'did1' } ) ;
254+ const identity2 = entityMockUtils . getIdentityInstance ( { did : 'did2' } ) ;
255+
256+ const asIdentitySpy = jest . spyOn ( utilsInternalModule , 'asIdentity' ) ;
257+ when ( asIdentitySpy ) . calledWith ( identity1 , mockContext ) . mockReturnValue ( identity1 ) ;
258+ when ( asIdentitySpy ) . calledWith ( identity2 , mockContext ) . mockReturnValue ( identity2 ) ;
259+
260+ await expect (
261+ prepareInitiateCorporateAction . call ( proc , {
262+ asset,
263+ declarationDate,
264+ description,
265+ kind,
266+ checkpoint,
267+ taxWithholdings : [
268+ { identity : identity1 , percentage : new BigNumber ( 10 ) } ,
269+ { identity : identity1 , percentage : new BigNumber ( 20 ) } , // Duplicate
270+ ] ,
271+ targets : null ,
272+ defaultTaxWithholding : null ,
273+ } )
274+ ) . rejects . toMatchObject ( {
275+ message : 'Identity included more than once in the tax withholding list' ,
276+ data : {
277+ identity : identity1 ,
278+ } ,
279+ } ) ;
280+
281+ asIdentitySpy . mockRestore ( ) ;
282+ } ) ;
283+
284+ it ( 'should validate tax withholdings limit' , async ( ) => {
285+ const proc = procedureMockUtils . getInstance < Params , CorporateAction > ( mockContext ) ;
286+
287+ const identity1 = entityMockUtils . getIdentityInstance ( { did : 'did1' } ) ;
288+ const identity2 = entityMockUtils . getIdentityInstance ( { did : 'did2' } ) ;
289+
290+ const asIdentitySpy = jest . spyOn ( utilsInternalModule , 'asIdentity' ) ;
291+ when ( asIdentitySpy ) . calledWith ( identity1 , mockContext ) . mockReturnValue ( identity1 ) ;
292+ when ( asIdentitySpy ) . calledWith ( identity2 , mockContext ) . mockReturnValue ( identity2 ) ;
293+
294+ // Set maxDidWhts to 1, but provide 2 tax withholdings
295+ dsMockUtils . setConstMock ( 'corporateAction' , 'maxDidWhts' , {
296+ returnValue : dsMockUtils . createMockU32 ( new BigNumber ( 1 ) ) ,
297+ } ) ;
298+
299+ await expect (
300+ prepareInitiateCorporateAction . call ( proc , {
301+ asset,
302+ declarationDate,
303+ description,
304+ kind,
305+ checkpoint,
306+ taxWithholdings : [
307+ { identity : identity1 , percentage : new BigNumber ( 10 ) } ,
308+ { identity : identity2 , percentage : new BigNumber ( 20 ) } ,
309+ ] ,
310+ targets : null ,
311+ defaultTaxWithholding : null ,
312+ } )
313+ ) . rejects . toThrow ( ) ; // Should throw from assertCaTaxWithholdingsValid
314+
315+ asIdentitySpy . mockRestore ( ) ;
316+ } ) ;
317+ } ) ;
318+
216319 describe ( 'initiateCorporateActionResolver' , ( ) => {
217320 let filterEventRecordsSpy : jest . SpyInstance ;
218321 let getCorporateActionWithDescriptionSpy : jest . SpyInstance ;
0 commit comments