@movebridge/testing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,988 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ PREDEFINED_SCHEMAS: () => PREDEFINED_SCHEMAS,
24
+ createCallTracker: () => createCallTracker,
25
+ createFaker: () => createFaker,
26
+ createIntegrationUtils: () => createIntegrationUtils,
27
+ createMockClient: () => createMockClient,
28
+ createNetworkSimulator: () => createNetworkSimulator,
29
+ createSnapshotUtils: () => createSnapshotUtils,
30
+ createTestHarness: () => createTestHarness,
31
+ getAddressValidationDetails: () => getAddressValidationDetails,
32
+ getValidationErrors: () => getValidationErrors,
33
+ hasSchema: () => hasSchema,
34
+ isValidAddress: () => isValidAddress,
35
+ normalizeAddress: () => normalizeAddress,
36
+ registerSchema: () => registerSchema,
37
+ validateAddress: () => validateAddress,
38
+ validateEntryFunctionPayload: () => validateEntryFunctionPayload,
39
+ validatePayload: () => validatePayload,
40
+ validateSchema: () => validateSchema,
41
+ validateTransferPayload: () => validateTransferPayload
42
+ });
43
+ module.exports = __toCommonJS(index_exports);
44
+
45
+ // src/faker.ts
46
+ var SeededRandom = class {
47
+ state;
48
+ constructor(seed) {
49
+ this.state = seed;
50
+ }
51
+ next() {
52
+ let t = this.state += 1831565813;
53
+ t = Math.imul(t ^ t >>> 15, t | 1);
54
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
55
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
56
+ }
57
+ nextInt(min, max) {
58
+ return Math.floor(this.next() * (max - min + 1)) + min;
59
+ }
60
+ nextBigInt(min, max) {
61
+ const range = max - min + 1n;
62
+ const randomFraction = this.next();
63
+ return min + BigInt(Math.floor(Number(range) * randomFraction));
64
+ }
65
+ pick(array2) {
66
+ return array2[this.nextInt(0, array2.length - 1)];
67
+ }
68
+ };
69
+ function createFaker(options) {
70
+ const seed = options?.seed ?? Date.now();
71
+ const random = new SeededRandom(seed);
72
+ function randomHex(length) {
73
+ const chars = "0123456789abcdef";
74
+ let result = "";
75
+ for (let i = 0; i < length; i++) {
76
+ result += chars[random.nextInt(0, 15)];
77
+ }
78
+ return result;
79
+ }
80
+ function randomTimestamp() {
81
+ const now = Date.now();
82
+ const dayAgo = now - 24 * 60 * 60 * 1e3;
83
+ return String(random.nextInt(dayAgo, now) * 1e3);
84
+ }
85
+ function randomSequenceNumber() {
86
+ return String(random.nextInt(0, 1e6));
87
+ }
88
+ return {
89
+ /**
90
+ * Generates a valid random Movement address
91
+ */
92
+ fakeAddress() {
93
+ return `0x${randomHex(64)}`;
94
+ },
95
+ /**
96
+ * Generates a random balance within optional bounds
97
+ */
98
+ fakeBalance(options2) {
99
+ const min = options2?.min ? BigInt(options2.min) : 0n;
100
+ const max = options2?.max ? BigInt(options2.max) : 10000000000n;
101
+ return random.nextBigInt(min, max).toString();
102
+ },
103
+ /**
104
+ * Generates a complete fake transaction
105
+ */
106
+ fakeTransaction() {
107
+ return {
108
+ hash: `0x${randomHex(64)}`,
109
+ sender: `0x${randomHex(64)}`,
110
+ sequenceNumber: randomSequenceNumber(),
111
+ payload: {
112
+ type: "entry_function_payload",
113
+ function: `0x${randomHex(64)}::module::function`,
114
+ typeArguments: [],
115
+ arguments: []
116
+ },
117
+ timestamp: randomTimestamp()
118
+ };
119
+ },
120
+ /**
121
+ * Generates a fake transaction response
122
+ */
123
+ fakeTransactionResponse(success = true) {
124
+ return {
125
+ hash: `0x${randomHex(64)}`,
126
+ success,
127
+ vmStatus: success ? "Executed successfully" : "Move abort",
128
+ gasUsed: String(random.nextInt(100, 1e4)),
129
+ events: []
130
+ };
131
+ },
132
+ /**
133
+ * Generates a fake resource with specified type
134
+ */
135
+ fakeResource(type) {
136
+ return {
137
+ type,
138
+ data: {
139
+ value: String(random.nextInt(0, 1e6))
140
+ }
141
+ };
142
+ },
143
+ /**
144
+ * Generates a fake contract event with specified type
145
+ */
146
+ fakeEvent(type) {
147
+ return {
148
+ type,
149
+ sequenceNumber: randomSequenceNumber(),
150
+ data: {
151
+ value: String(random.nextInt(0, 1e6))
152
+ }
153
+ };
154
+ },
155
+ /**
156
+ * Generates a fake wallet state
157
+ */
158
+ fakeWalletState(connected = false) {
159
+ if (connected) {
160
+ return {
161
+ connected: true,
162
+ address: `0x${randomHex(64)}`,
163
+ publicKey: `0x${randomHex(64)}`
164
+ };
165
+ }
166
+ return {
167
+ connected: false,
168
+ address: null,
169
+ publicKey: null
170
+ };
171
+ },
172
+ /**
173
+ * Generates a fake transaction hash
174
+ */
175
+ fakeTransactionHash() {
176
+ return `0x${randomHex(64)}`;
177
+ }
178
+ };
179
+ }
180
+
181
+ // src/tracker.ts
182
+ var import_core = require("@movebridge/core");
183
+ function createCallTracker() {
184
+ const calls = [];
185
+ function deepEqual(a, b) {
186
+ if (a === b) return true;
187
+ if (a === null || b === null) return false;
188
+ if (typeof a !== typeof b) return false;
189
+ if (Array.isArray(a) && Array.isArray(b)) {
190
+ if (a.length !== b.length) return false;
191
+ return a.every((item, index) => deepEqual(item, b[index]));
192
+ }
193
+ if (typeof a === "object" && typeof b === "object") {
194
+ const aKeys = Object.keys(a);
195
+ const bKeys = Object.keys(b);
196
+ if (aKeys.length !== bKeys.length) return false;
197
+ return aKeys.every(
198
+ (key) => deepEqual(
199
+ a[key],
200
+ b[key]
201
+ )
202
+ );
203
+ }
204
+ return false;
205
+ }
206
+ return {
207
+ /**
208
+ * Records a method call
209
+ */
210
+ recordCall(method, args, result, error) {
211
+ calls.push({
212
+ method,
213
+ arguments: args,
214
+ timestamp: Date.now(),
215
+ result,
216
+ error
217
+ });
218
+ },
219
+ /**
220
+ * Gets all calls to a specific method
221
+ */
222
+ getCalls(method) {
223
+ return calls.filter((call) => call.method === method);
224
+ },
225
+ /**
226
+ * Gets the count of calls to a specific method
227
+ */
228
+ getCallCount(method) {
229
+ return calls.filter((call) => call.method === method).length;
230
+ },
231
+ /**
232
+ * Gets all recorded calls
233
+ */
234
+ getAllCalls() {
235
+ return [...calls];
236
+ },
237
+ /**
238
+ * Asserts that a method was called at least once
239
+ */
240
+ assertCalled(method) {
241
+ const count = this.getCallCount(method);
242
+ if (count === 0) {
243
+ throw new import_core.MovementError(
244
+ `Expected method "${method}" to be called, but it was never called`,
245
+ "INVALID_ARGUMENT",
246
+ { method, expected: "at least 1 call", actual: 0 }
247
+ );
248
+ }
249
+ },
250
+ /**
251
+ * Asserts that a method was called exactly N times
252
+ */
253
+ assertCalledTimes(method, times) {
254
+ const count = this.getCallCount(method);
255
+ if (count !== times) {
256
+ throw new import_core.MovementError(
257
+ `Expected method "${method}" to be called ${times} times, but it was called ${count} times`,
258
+ "INVALID_ARGUMENT",
259
+ { method, expected: times, actual: count }
260
+ );
261
+ }
262
+ },
263
+ /**
264
+ * Asserts that a method was called with specific arguments
265
+ */
266
+ assertCalledWith(method, ...expectedArgs) {
267
+ const methodCalls = this.getCalls(method);
268
+ if (methodCalls.length === 0) {
269
+ throw new import_core.MovementError(
270
+ `Expected method "${method}" to be called with arguments, but it was never called`,
271
+ "INVALID_ARGUMENT",
272
+ { method, expectedArgs, actualCalls: [] }
273
+ );
274
+ }
275
+ const hasMatch = methodCalls.some(
276
+ (call) => deepEqual(call.arguments, expectedArgs)
277
+ );
278
+ if (!hasMatch) {
279
+ throw new import_core.MovementError(
280
+ `Expected method "${method}" to be called with specific arguments, but no matching call found`,
281
+ "INVALID_ARGUMENT",
282
+ {
283
+ method,
284
+ expectedArgs,
285
+ actualCalls: methodCalls.map((c) => c.arguments)
286
+ }
287
+ );
288
+ }
289
+ },
290
+ /**
291
+ * Asserts that a method was never called
292
+ */
293
+ assertNotCalled(method) {
294
+ const count = this.getCallCount(method);
295
+ if (count > 0) {
296
+ throw new import_core.MovementError(
297
+ `Expected method "${method}" to not be called, but it was called ${count} times`,
298
+ "INVALID_ARGUMENT",
299
+ { method, expected: 0, actual: count }
300
+ );
301
+ }
302
+ },
303
+ /**
304
+ * Clears all recorded calls
305
+ */
306
+ clearCalls() {
307
+ calls.length = 0;
308
+ }
309
+ };
310
+ }
311
+
312
+ // src/simulator.ts
313
+ function createNetworkSimulator() {
314
+ let latency = 0;
315
+ let networkErrorEnabled = false;
316
+ let rateLimitMax = null;
317
+ let rateLimitCalls = 0;
318
+ const timeoutMethods = /* @__PURE__ */ new Set();
319
+ return {
320
+ /**
321
+ * Sets the latency for all mock responses
322
+ */
323
+ simulateLatency(ms) {
324
+ latency = ms;
325
+ },
326
+ /**
327
+ * Causes a specific method to timeout
328
+ */
329
+ simulateTimeout(method) {
330
+ timeoutMethods.add(method);
331
+ },
332
+ /**
333
+ * Enables network error simulation for all calls
334
+ */
335
+ simulateNetworkError() {
336
+ networkErrorEnabled = true;
337
+ },
338
+ /**
339
+ * Sets up rate limiting after N calls
340
+ */
341
+ simulateRateLimit(maxCalls) {
342
+ rateLimitMax = maxCalls;
343
+ rateLimitCalls = 0;
344
+ },
345
+ /**
346
+ * Resets all simulation settings
347
+ */
348
+ resetSimulation() {
349
+ latency = 0;
350
+ networkErrorEnabled = false;
351
+ rateLimitMax = null;
352
+ rateLimitCalls = 0;
353
+ timeoutMethods.clear();
354
+ },
355
+ /**
356
+ * Gets the current latency setting
357
+ */
358
+ getLatency() {
359
+ return latency;
360
+ },
361
+ /**
362
+ * Checks if network error is enabled
363
+ */
364
+ isNetworkErrorEnabled() {
365
+ return networkErrorEnabled;
366
+ },
367
+ /**
368
+ * Checks if a method is set to timeout
369
+ */
370
+ isMethodTimedOut(method) {
371
+ return timeoutMethods.has(method);
372
+ },
373
+ /**
374
+ * Gets remaining rate limit calls (null if no limit)
375
+ */
376
+ getRateLimitRemaining() {
377
+ if (rateLimitMax === null) return null;
378
+ return Math.max(0, rateLimitMax - rateLimitCalls);
379
+ },
380
+ /**
381
+ * Increments the rate limit call counter
382
+ */
383
+ incrementRateLimitCalls() {
384
+ rateLimitCalls++;
385
+ }
386
+ };
387
+ }
388
+ async function applySimulation(simulator, method, fn) {
389
+ if (simulator.isNetworkErrorEnabled()) {
390
+ throw new Error("Network error: Connection failed");
391
+ }
392
+ if (simulator.isMethodTimedOut(method)) {
393
+ throw new Error(`Timeout: Method "${method}" timed out`);
394
+ }
395
+ const remaining = simulator.getRateLimitRemaining();
396
+ if (remaining !== null && remaining <= 0) {
397
+ throw new Error("Rate limited: Too many requests");
398
+ }
399
+ if (remaining !== null) {
400
+ simulator.incrementRateLimitCalls();
401
+ }
402
+ const latency = simulator.getLatency();
403
+ if (latency > 0) {
404
+ await new Promise((resolve) => setTimeout(resolve, latency));
405
+ }
406
+ return fn();
407
+ }
408
+
409
+ // src/mock-client.ts
410
+ function createMockClient(deps) {
411
+ const { tracker, simulator, faker } = deps;
412
+ const mocks = /* @__PURE__ */ new Map();
413
+ const onceMocks = /* @__PURE__ */ new Map();
414
+ async function getResponse(method, defaultFn) {
415
+ const onceMock = onceMocks.get(method);
416
+ if (onceMock) {
417
+ onceMocks.delete(method);
418
+ if (onceMock.error) {
419
+ throw onceMock.error;
420
+ }
421
+ return onceMock.response;
422
+ }
423
+ const mock = mocks.get(method);
424
+ if (mock) {
425
+ if (mock.error) {
426
+ throw mock.error;
427
+ }
428
+ return mock.response;
429
+ }
430
+ return defaultFn();
431
+ }
432
+ async function wrapCall(method, args, defaultFn) {
433
+ try {
434
+ const result = await applySimulation(simulator, method, async () => {
435
+ return getResponse(method, defaultFn);
436
+ });
437
+ tracker.recordCall(method, args, result);
438
+ return result;
439
+ } catch (error) {
440
+ tracker.recordCall(method, args, void 0, error);
441
+ throw error;
442
+ }
443
+ }
444
+ return {
445
+ /**
446
+ * Configures a persistent mock response for a method
447
+ */
448
+ mockResponse(method, response) {
449
+ mocks.set(method, { response });
450
+ },
451
+ /**
452
+ * Configures a mock error for a method
453
+ */
454
+ mockError(method, error) {
455
+ mocks.set(method, { error });
456
+ },
457
+ /**
458
+ * Configures a one-time mock response for a method
459
+ */
460
+ mockResponseOnce(method, response) {
461
+ onceMocks.set(method, { response });
462
+ },
463
+ /**
464
+ * Clears all mock configurations
465
+ */
466
+ clearMocks() {
467
+ mocks.clear();
468
+ onceMocks.clear();
469
+ },
470
+ /**
471
+ * Gets account balance (mocked)
472
+ */
473
+ async getAccountBalance(address) {
474
+ return wrapCall(
475
+ "getAccountBalance",
476
+ [address],
477
+ () => faker.fakeBalance()
478
+ );
479
+ },
480
+ /**
481
+ * Gets account resources (mocked)
482
+ */
483
+ async getAccountResources(address) {
484
+ return wrapCall("getAccountResources", [address], () => [
485
+ faker.fakeResource("0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
486
+ ]);
487
+ },
488
+ /**
489
+ * Gets transaction by hash (mocked)
490
+ */
491
+ async getTransaction(hash) {
492
+ return wrapCall("getTransaction", [hash], () => {
493
+ const tx = faker.fakeTransaction();
494
+ return { ...tx, hash };
495
+ });
496
+ },
497
+ /**
498
+ * Waits for transaction confirmation (mocked)
499
+ */
500
+ async waitForTransaction(hash) {
501
+ return wrapCall("waitForTransaction", [hash], () => {
502
+ const response = faker.fakeTransactionResponse(true);
503
+ return { ...response, hash };
504
+ });
505
+ }
506
+ };
507
+ }
508
+
509
+ // src/harness.ts
510
+ function createTestHarness(config) {
511
+ const seed = config?.seed ?? Date.now();
512
+ const defaultLatency = config?.defaultLatency ?? 0;
513
+ const faker = createFaker({ seed });
514
+ const tracker = createCallTracker();
515
+ const simulator = createNetworkSimulator();
516
+ if (defaultLatency > 0) {
517
+ simulator.simulateLatency(defaultLatency);
518
+ }
519
+ const client = createMockClient({ tracker, simulator, faker });
520
+ return {
521
+ client,
522
+ tracker,
523
+ simulator,
524
+ faker,
525
+ /**
526
+ * Cleans up all state - call after each test
527
+ */
528
+ cleanup() {
529
+ client.clearMocks();
530
+ tracker.clearCalls();
531
+ simulator.resetSimulation();
532
+ },
533
+ /**
534
+ * Resets to initial state while keeping configuration
535
+ */
536
+ reset() {
537
+ client.clearMocks();
538
+ tracker.clearCalls();
539
+ }
540
+ };
541
+ }
542
+
543
+ // src/validators/address.ts
544
+ var import_core2 = require("@movebridge/core");
545
+ var ADDRESS_REGEX = /^(0[xX])?[a-fA-F0-9]{1,64}$/;
546
+ function isValidAddress(address) {
547
+ if (typeof address !== "string") {
548
+ return false;
549
+ }
550
+ return ADDRESS_REGEX.test(address);
551
+ }
552
+ function validateAddress(address) {
553
+ const result = getAddressValidationDetails(address);
554
+ if (!result.valid) {
555
+ throw new import_core2.MovementError(
556
+ `Invalid address: ${address} - ${result.error}`,
557
+ "INVALID_ADDRESS",
558
+ { address, reason: result.error }
559
+ );
560
+ }
561
+ }
562
+ function normalizeAddress(address) {
563
+ validateAddress(address);
564
+ const withoutPrefix = address.startsWith("0x") || address.startsWith("0X") ? address.slice(2) : address;
565
+ const padded = withoutPrefix.padStart(64, "0");
566
+ return `0x${padded.toLowerCase()}`;
567
+ }
568
+ function getAddressValidationDetails(address) {
569
+ if (typeof address !== "string") {
570
+ return {
571
+ valid: false,
572
+ error: "Address must be a string"
573
+ };
574
+ }
575
+ if (address.length === 0) {
576
+ return {
577
+ valid: false,
578
+ error: "Address cannot be empty"
579
+ };
580
+ }
581
+ const hasPrefix = address.startsWith("0x") || address.startsWith("0X");
582
+ const hexPart = hasPrefix ? address.slice(2) : address;
583
+ if (hexPart.length === 0) {
584
+ return {
585
+ valid: false,
586
+ error: "Address must contain hex characters after 0x prefix"
587
+ };
588
+ }
589
+ if (hexPart.length > 64) {
590
+ return {
591
+ valid: false,
592
+ error: `Address too long: ${hexPart.length} hex characters (max 64)`
593
+ };
594
+ }
595
+ if (!/^[a-fA-F0-9]+$/.test(hexPart)) {
596
+ return {
597
+ valid: false,
598
+ error: "Address contains invalid characters (must be hex: 0-9, a-f, A-F)"
599
+ };
600
+ }
601
+ const normalized = `0x${hexPart.padStart(64, "0").toLowerCase()}`;
602
+ return {
603
+ valid: true,
604
+ normalized
605
+ };
606
+ }
607
+
608
+ // src/validators/transaction.ts
609
+ var import_core3 = require("@movebridge/core");
610
+ var FUNCTION_REGEX = /^0x[a-fA-F0-9]{1,64}::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$/;
611
+ function validateTransferPayload(payload) {
612
+ if (!isValidAddress(payload.to)) {
613
+ throw new import_core3.MovementError(
614
+ `Invalid recipient address: ${payload.to}`,
615
+ "INVALID_ADDRESS",
616
+ { address: payload.to, field: "to" }
617
+ );
618
+ }
619
+ validateAmount(payload.amount);
620
+ if (payload.coinType !== void 0) {
621
+ if (typeof payload.coinType !== "string" || payload.coinType.length === 0) {
622
+ throw new import_core3.MovementError(
623
+ "Invalid coin type: must be a non-empty string",
624
+ "INVALID_ARGUMENT",
625
+ { argument: "coinType", value: payload.coinType }
626
+ );
627
+ }
628
+ }
629
+ return true;
630
+ }
631
+ function validateEntryFunctionPayload(payload) {
632
+ if (!FUNCTION_REGEX.test(payload.function)) {
633
+ throw new import_core3.MovementError(
634
+ `Invalid function identifier: ${payload.function}`,
635
+ "INVALID_ARGUMENT",
636
+ {
637
+ argument: "function",
638
+ value: payload.function,
639
+ expectedFormat: "0xADDRESS::module::function"
640
+ }
641
+ );
642
+ }
643
+ if (!Array.isArray(payload.typeArguments)) {
644
+ throw new import_core3.MovementError(
645
+ "typeArguments must be an array",
646
+ "INVALID_ARGUMENT",
647
+ { argument: "typeArguments", value: payload.typeArguments }
648
+ );
649
+ }
650
+ for (let i = 0; i < payload.typeArguments.length; i++) {
651
+ if (typeof payload.typeArguments[i] !== "string") {
652
+ throw new import_core3.MovementError(
653
+ `typeArguments[${i}] must be a string`,
654
+ "INVALID_ARGUMENT",
655
+ { argument: `typeArguments[${i}]`, value: payload.typeArguments[i] }
656
+ );
657
+ }
658
+ }
659
+ if (!Array.isArray(payload.arguments)) {
660
+ throw new import_core3.MovementError(
661
+ "arguments must be an array",
662
+ "INVALID_ARGUMENT",
663
+ { argument: "arguments", value: payload.arguments }
664
+ );
665
+ }
666
+ return true;
667
+ }
668
+ function validatePayload(payload) {
669
+ if (!payload || typeof payload !== "object") {
670
+ throw new import_core3.MovementError(
671
+ "Payload must be an object",
672
+ "INVALID_ARGUMENT",
673
+ { argument: "payload", value: payload }
674
+ );
675
+ }
676
+ if (payload.type !== "entry_function_payload") {
677
+ throw new import_core3.MovementError(
678
+ `Unsupported payload type: ${payload.type}`,
679
+ "INVALID_ARGUMENT",
680
+ { argument: "type", value: payload.type, supported: ["entry_function_payload"] }
681
+ );
682
+ }
683
+ return validateEntryFunctionPayload({
684
+ function: payload.function,
685
+ typeArguments: payload.typeArguments,
686
+ arguments: payload.arguments
687
+ });
688
+ }
689
+ function validateAmount(amount) {
690
+ if (typeof amount !== "string") {
691
+ throw new import_core3.MovementError(
692
+ "Amount must be a string",
693
+ "INVALID_ARGUMENT",
694
+ { argument: "amount", value: amount }
695
+ );
696
+ }
697
+ const numValue = Number(amount);
698
+ if (isNaN(numValue)) {
699
+ throw new import_core3.MovementError(
700
+ `Invalid amount: "${amount}" is not a valid number`,
701
+ "INVALID_ARGUMENT",
702
+ { argument: "amount", value: amount, reason: "not a number" }
703
+ );
704
+ }
705
+ if (numValue < 0) {
706
+ throw new import_core3.MovementError(
707
+ `Invalid amount: "${amount}" cannot be negative`,
708
+ "INVALID_ARGUMENT",
709
+ { argument: "amount", value: amount, reason: "negative value" }
710
+ );
711
+ }
712
+ if (numValue === 0) {
713
+ throw new import_core3.MovementError(
714
+ `Invalid amount: "${amount}" cannot be zero`,
715
+ "INVALID_ARGUMENT",
716
+ { argument: "amount", value: amount, reason: "zero value" }
717
+ );
718
+ }
719
+ if (!Number.isInteger(numValue)) {
720
+ throw new import_core3.MovementError(
721
+ `Invalid amount: "${amount}" must be a whole number (no decimals)`,
722
+ "INVALID_ARGUMENT",
723
+ { argument: "amount", value: amount, reason: "contains decimals" }
724
+ );
725
+ }
726
+ }
727
+
728
+ // src/validators/schema.ts
729
+ var import_superstruct = require("superstruct");
730
+ var import_core4 = require("@movebridge/core");
731
+ var PREDEFINED_SCHEMAS = {
732
+ Resource: (0, import_superstruct.object)({
733
+ type: (0, import_superstruct.string)(),
734
+ data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
735
+ }),
736
+ Transaction: (0, import_superstruct.object)({
737
+ hash: (0, import_superstruct.string)(),
738
+ sender: (0, import_superstruct.string)(),
739
+ sequenceNumber: (0, import_superstruct.string)(),
740
+ payload: (0, import_superstruct.object)({
741
+ type: (0, import_superstruct.literal)("entry_function_payload"),
742
+ function: (0, import_superstruct.string)(),
743
+ typeArguments: (0, import_superstruct.array)((0, import_superstruct.string)()),
744
+ arguments: (0, import_superstruct.array)((0, import_superstruct.unknown)())
745
+ }),
746
+ timestamp: (0, import_superstruct.string)()
747
+ }),
748
+ TransactionResponse: (0, import_superstruct.object)({
749
+ hash: (0, import_superstruct.string)(),
750
+ success: (0, import_superstruct.boolean)(),
751
+ vmStatus: (0, import_superstruct.string)(),
752
+ gasUsed: (0, import_superstruct.string)(),
753
+ events: (0, import_superstruct.array)(
754
+ (0, import_superstruct.object)({
755
+ type: (0, import_superstruct.string)(),
756
+ sequenceNumber: (0, import_superstruct.string)(),
757
+ data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
758
+ })
759
+ )
760
+ }),
761
+ WalletState: (0, import_superstruct.object)({
762
+ connected: (0, import_superstruct.boolean)(),
763
+ address: (0, import_superstruct.nullable)((0, import_superstruct.string)()),
764
+ publicKey: (0, import_superstruct.nullable)((0, import_superstruct.string)())
765
+ }),
766
+ ContractEvent: (0, import_superstruct.object)({
767
+ type: (0, import_superstruct.string)(),
768
+ sequenceNumber: (0, import_superstruct.string)(),
769
+ data: (0, import_superstruct.record)((0, import_superstruct.string)(), (0, import_superstruct.unknown)())
770
+ })
771
+ };
772
+ var customSchemas = /* @__PURE__ */ new Map();
773
+ function getSchema(name) {
774
+ if (name in PREDEFINED_SCHEMAS) {
775
+ return PREDEFINED_SCHEMAS[name];
776
+ }
777
+ const customSchema = customSchemas.get(name);
778
+ if (customSchema) {
779
+ return customSchema;
780
+ }
781
+ throw new import_core4.MovementError(
782
+ `Unknown schema: ${name}`,
783
+ "INVALID_ARGUMENT",
784
+ {
785
+ schemaName: name,
786
+ availableSchemas: [
787
+ ...Object.keys(PREDEFINED_SCHEMAS),
788
+ ...customSchemas.keys()
789
+ ]
790
+ }
791
+ );
792
+ }
793
+ function validateSchema(data, schemaName) {
794
+ const schema = getSchema(schemaName);
795
+ const [error] = (0, import_superstruct.validate)(data, schema);
796
+ return error === void 0;
797
+ }
798
+ function getValidationErrors(data, schemaName) {
799
+ const schema = getSchema(schemaName);
800
+ const [error] = (0, import_superstruct.validate)(data, schema);
801
+ if (!error) {
802
+ return [];
803
+ }
804
+ return convertStructError(error);
805
+ }
806
+ function registerSchema(name, schema) {
807
+ if (name in PREDEFINED_SCHEMAS) {
808
+ throw new import_core4.MovementError(
809
+ `Cannot override predefined schema: ${name}`,
810
+ "INVALID_ARGUMENT",
811
+ { schemaName: name }
812
+ );
813
+ }
814
+ customSchemas.set(name, schema);
815
+ }
816
+ function hasSchema(name) {
817
+ return name in PREDEFINED_SCHEMAS || customSchemas.has(name);
818
+ }
819
+ function convertStructError(error) {
820
+ const errors = [];
821
+ for (const failure of error.failures()) {
822
+ errors.push({
823
+ path: failure.path.join(".") || "(root)",
824
+ expected: failure.type,
825
+ received: getTypeOf(failure.value),
826
+ message: failure.message
827
+ });
828
+ }
829
+ return errors;
830
+ }
831
+ function getTypeOf(value) {
832
+ if (value === null) return "null";
833
+ if (value === void 0) return "undefined";
834
+ if (Array.isArray(value)) return "array";
835
+ return typeof value;
836
+ }
837
+
838
+ // src/snapshots.ts
839
+ function createSnapshotUtils() {
840
+ const snapshots = /* @__PURE__ */ new Map();
841
+ function serialize(data) {
842
+ return JSON.stringify(data, null, 2);
843
+ }
844
+ function generateDiff(expected, actual) {
845
+ const expectedLines = expected.split("\n");
846
+ const actualLines = actual.split("\n");
847
+ const diff = [];
848
+ const maxLines = Math.max(expectedLines.length, actualLines.length);
849
+ for (let i = 0; i < maxLines; i++) {
850
+ const expectedLine = expectedLines[i];
851
+ const actualLine = actualLines[i];
852
+ if (expectedLine === actualLine) {
853
+ diff.push(` ${expectedLine ?? ""}`);
854
+ } else {
855
+ if (expectedLine !== void 0) {
856
+ diff.push(`- ${expectedLine}`);
857
+ }
858
+ if (actualLine !== void 0) {
859
+ diff.push(`+ ${actualLine}`);
860
+ }
861
+ }
862
+ }
863
+ return diff.join("\n");
864
+ }
865
+ return {
866
+ /**
867
+ * Creates a new snapshot
868
+ */
869
+ createSnapshot(data, name) {
870
+ snapshots.set(name, serialize(data));
871
+ },
872
+ /**
873
+ * Matches data against an existing snapshot
874
+ * Creates a new snapshot if one doesn't exist
875
+ */
876
+ matchSnapshot(data, name) {
877
+ const serialized = serialize(data);
878
+ const existing = snapshots.get(name);
879
+ if (existing === void 0) {
880
+ snapshots.set(name, serialized);
881
+ return { match: true };
882
+ }
883
+ if (existing === serialized) {
884
+ return { match: true };
885
+ }
886
+ return {
887
+ match: false,
888
+ diff: generateDiff(existing, serialized)
889
+ };
890
+ },
891
+ /**
892
+ * Updates an existing snapshot with new data
893
+ */
894
+ updateSnapshot(data, name) {
895
+ snapshots.set(name, serialize(data));
896
+ },
897
+ /**
898
+ * Deletes a snapshot
899
+ */
900
+ deleteSnapshot(name) {
901
+ snapshots.delete(name);
902
+ },
903
+ /**
904
+ * Lists all snapshot names
905
+ */
906
+ listSnapshots() {
907
+ return Array.from(snapshots.keys());
908
+ }
909
+ };
910
+ }
911
+
912
+ // src/integration.ts
913
+ var import_core5 = require("@movebridge/core");
914
+ function createIntegrationUtils(network) {
915
+ if (network !== "testnet") {
916
+ throw new import_core5.MovementError(
917
+ "Integration tests can only be run on testnet to prevent accidental mainnet usage",
918
+ "INVALID_ARGUMENT",
919
+ { network, allowed: ["testnet"] }
920
+ );
921
+ }
922
+ return {
923
+ /**
924
+ * Creates a new test account and funds it from the faucet
925
+ * Note: This requires network access and should only be used in integration tests
926
+ */
927
+ async createTestAccount() {
928
+ throw new import_core5.MovementError(
929
+ "createTestAccount requires network access. Use in integration tests only.",
930
+ "NETWORK_ERROR",
931
+ { reason: "Not implemented for unit tests" }
932
+ );
933
+ },
934
+ /**
935
+ * Waits for an account to be funded
936
+ */
937
+ async waitForFunding(address, timeout = 3e4) {
938
+ throw new import_core5.MovementError(
939
+ "waitForFunding requires network access. Use in integration tests only.",
940
+ "NETWORK_ERROR",
941
+ { address, timeout, reason: "Not implemented for unit tests" }
942
+ );
943
+ },
944
+ /**
945
+ * Cleans up a test account by transferring remaining balance
946
+ */
947
+ async cleanupTestAccount(account) {
948
+ throw new import_core5.MovementError(
949
+ "cleanupTestAccount requires network access. Use in integration tests only.",
950
+ "NETWORK_ERROR",
951
+ { address: account.address, reason: "Not implemented for unit tests" }
952
+ );
953
+ },
954
+ /**
955
+ * Executes a callback with a temporary test account
956
+ */
957
+ async withTestAccount(callback) {
958
+ const account = await this.createTestAccount();
959
+ try {
960
+ return await callback(account);
961
+ } finally {
962
+ await this.cleanupTestAccount(account);
963
+ }
964
+ }
965
+ };
966
+ }
967
+ // Annotate the CommonJS export names for ESM import in node:
968
+ 0 && (module.exports = {
969
+ PREDEFINED_SCHEMAS,
970
+ createCallTracker,
971
+ createFaker,
972
+ createIntegrationUtils,
973
+ createMockClient,
974
+ createNetworkSimulator,
975
+ createSnapshotUtils,
976
+ createTestHarness,
977
+ getAddressValidationDetails,
978
+ getValidationErrors,
979
+ hasSchema,
980
+ isValidAddress,
981
+ normalizeAddress,
982
+ registerSchema,
983
+ validateAddress,
984
+ validateEntryFunctionPayload,
985
+ validatePayload,
986
+ validateSchema,
987
+ validateTransferPayload
988
+ });