aws-sdk-vitest-mock 0.4.0 → 0.6.0
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/README.md +225 -42
- package/index.cjs +18 -1
- package/index.d.ts +46 -1
- package/index.js +350 -236
- package/lib/matchers.d.ts +141 -0
- package/lib/mock-client.d.ts +457 -19
- package/lib/utils/debug-logger.d.ts +5 -3
- package/lib/utils/file-helpers.d.ts +1 -1
- package/lib/utils/paginator-helpers.d.ts +85 -1
- package/lib/utils/stream-helpers.d.ts +1 -1
- package/{matchers-Rq18z2C7.cjs → matchers-CNhdB_9q.cjs} +2 -2
- package/{matchers-C6AtmwWz.js → matchers-DmTtFk31.js} +101 -2
- package/package.json +11 -2
- package/vitest-setup.cjs +1 -1
- package/vitest-setup.js +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
A powerful, type-safe mocking library for AWS SDK v3 with Vitest
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/aws-sdk-vitest-mock">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/aws-sdk-vitest-mock?color=cb3837&logo=npm" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/sudokar/aws-sdk-vitest-mock/actions">
|
|
16
|
+
<img src="https://github.com/sudokar/aws-sdk-vitest-mock/actions/workflows/ci.yml/badge.svg" alt="CI Status" />
|
|
17
|
+
</a>
|
|
18
|
+
<img src="https://img.shields.io/badge/ESM%20Support-yes-4B32C3?logo=javascript" alt="ESM Support" />
|
|
19
|
+
<img src="https://img.shields.io/badge/Zero%20Dependencies-yes-brightgreen" alt="Zero Dependencies" />
|
|
20
|
+
<a href="https://eslint.org/">
|
|
21
|
+
<img src="https://img.shields.io/badge/code%20style-eslint-4B32C3?logo=eslint" alt="ESLint" />
|
|
22
|
+
</a>
|
|
23
|
+
<a href="https://prettier.io/">
|
|
24
|
+
<img src="https://img.shields.io/badge/code%20style-prettier-F7B93E?logo=prettier" alt="Prettier" />
|
|
25
|
+
</a>
|
|
26
|
+
<img src="https://img.shields.io/badge/Maintained-yes-brightgreen" alt="Maintained: Yes" />
|
|
27
|
+
</p>
|
|
28
|
+
|
|
11
29
|
---
|
|
12
30
|
|
|
13
31
|
## ✨ Features
|
|
@@ -104,6 +122,9 @@ Understanding these concepts will help you use the library effectively:
|
|
|
104
122
|
- **Command Matching** - Commands are matched by constructor. Optionally match by input properties (partial matching by default, strict matching available).
|
|
105
123
|
- **Sequential Responses** - Use `resolvesOnce()` / `rejectsOnce()` for one-time behaviors that fall back to permanent handlers set with `resolves()` / `rejects()`.
|
|
106
124
|
- **Chainable API** - All mock configuration methods return the stub, allowing method chaining for cleaner test setup.
|
|
125
|
+
- **Test Lifecycle**:
|
|
126
|
+
- **`reset()`** - Clears call history while preserving mock configurations. Use when you want to verify multiple test scenarios with the same mock setup.
|
|
127
|
+
- **`restore()`** - Completely removes mocking and restores original client behavior. Use in `afterEach()` to clean up between tests.
|
|
107
128
|
|
|
108
129
|
## 📖 Usage Guide
|
|
109
130
|
|
|
@@ -190,6 +211,39 @@ test("should mock existing S3 client instance", async () => {
|
|
|
190
211
|
});
|
|
191
212
|
```
|
|
192
213
|
|
|
214
|
+
### Test Lifecycle Management
|
|
215
|
+
|
|
216
|
+
Use `reset()` to clear call history between assertions while keeping mock configurations. Use `restore()` to completely clean up mocking:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
test("should handle multiple operations with same mock", async () => {
|
|
220
|
+
const s3Mock = mockClient(S3Client);
|
|
221
|
+
const client = new S3Client({});
|
|
222
|
+
|
|
223
|
+
// Configure mock once
|
|
224
|
+
s3Mock.on(GetObjectCommand).resolves({ Body: "file-content" });
|
|
225
|
+
|
|
226
|
+
// First operation
|
|
227
|
+
await client.send(
|
|
228
|
+
new GetObjectCommand({ Bucket: "bucket", Key: "file1.txt" }),
|
|
229
|
+
);
|
|
230
|
+
expect(s3Mock).toHaveReceivedCommandTimes(GetObjectCommand, 1);
|
|
231
|
+
|
|
232
|
+
// Reset clears call history but keeps mock configuration
|
|
233
|
+
s3Mock.reset();
|
|
234
|
+
expect(s3Mock).toHaveReceivedCommandTimes(GetObjectCommand, 0);
|
|
235
|
+
|
|
236
|
+
// Second operation - mock still works
|
|
237
|
+
await client.send(
|
|
238
|
+
new GetObjectCommand({ Bucket: "bucket", Key: "file2.txt" }),
|
|
239
|
+
);
|
|
240
|
+
expect(s3Mock).toHaveReceivedCommandTimes(GetObjectCommand, 1);
|
|
241
|
+
|
|
242
|
+
// Clean up completely
|
|
243
|
+
s3Mock.restore();
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
193
247
|
## 🔧 AWS Service Examples
|
|
194
248
|
|
|
195
249
|
### DynamoDB with Marshal/Unmarshal
|
|
@@ -311,55 +365,94 @@ s3Mock
|
|
|
311
365
|
|
|
312
366
|
### Paginator Support
|
|
313
367
|
|
|
314
|
-
Mock AWS SDK v3 pagination with automatic token handling
|
|
368
|
+
Mock AWS SDK v3 pagination with automatic token handling. **Tokens are the actual last item from each page** (works for both DynamoDB and S3).
|
|
369
|
+
|
|
370
|
+
#### DynamoDB Pagination
|
|
315
371
|
|
|
316
372
|
```typescript
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const items = Array.from({ length: 25 }, (_, i) => ({
|
|
320
|
-
id: { S: `item-${i + 1}` },
|
|
321
|
-
}));
|
|
373
|
+
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
|
|
374
|
+
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
|
|
322
375
|
|
|
323
|
-
|
|
324
|
-
|
|
376
|
+
// Create marshalled items (as they would be stored in DynamoDB)
|
|
377
|
+
const users = [
|
|
378
|
+
{ id: "user-1", name: "Alice", email: "alice@example.com" },
|
|
379
|
+
{ id: "user-2", name: "Bob", email: "bob@example.com" },
|
|
380
|
+
{ id: "user-3", name: "Charlie", email: "charlie@example.com" },
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
const marshalledUsers = users.map(user => marshall(user));
|
|
384
|
+
|
|
385
|
+
// Configure pagination
|
|
386
|
+
dynamoMock.on(ScanCommand).resolvesPaginated(marshalledUsers, {
|
|
387
|
+
pageSize: 1,
|
|
325
388
|
itemsKey: "Items",
|
|
326
|
-
tokenKey: "LastEvaluatedKey",
|
|
327
|
-
inputTokenKey: "ExclusiveStartKey"
|
|
389
|
+
tokenKey: "LastEvaluatedKey", // DynamoDB response key
|
|
390
|
+
inputTokenKey: "ExclusiveStartKey" // DynamoDB request key
|
|
328
391
|
});
|
|
329
392
|
|
|
330
|
-
//
|
|
331
|
-
const
|
|
332
|
-
|
|
393
|
+
// Page 1: Get first user
|
|
394
|
+
const page1 = await client.send(new ScanCommand({ TableName: "Users" }));
|
|
395
|
+
expect(page1.Items).toHaveLength(1);
|
|
396
|
+
// LastEvaluatedKey is the marshalled last item (object, not string!)
|
|
397
|
+
expect(page1.LastEvaluatedKey).toEqual(marshall({ id: "user-1", name: "Alice", ... }));
|
|
333
398
|
|
|
334
|
-
//
|
|
335
|
-
const
|
|
399
|
+
// Unmarshall the items
|
|
400
|
+
const page1Users = page1.Items.map(item => unmarshall(item));
|
|
401
|
+
console.log(page1Users[0]); // { id: "user-1", name: "Alice", ... }
|
|
402
|
+
|
|
403
|
+
// Page 2: Use LastEvaluatedKey to get next page
|
|
404
|
+
const page2 = await client.send(
|
|
336
405
|
new ScanCommand({
|
|
337
|
-
TableName: "
|
|
338
|
-
ExclusiveStartKey:
|
|
339
|
-
})
|
|
406
|
+
TableName: "Users",
|
|
407
|
+
ExclusiveStartKey: page1.LastEvaluatedKey, // Pass the object directly
|
|
408
|
+
})
|
|
340
409
|
);
|
|
341
|
-
// result2.LastEvaluatedKey = "token-20"
|
|
342
410
|
|
|
343
|
-
//
|
|
344
|
-
const
|
|
411
|
+
// Page 3: Continue until LastEvaluatedKey is undefined
|
|
412
|
+
const page3 = await client.send(
|
|
345
413
|
new ScanCommand({
|
|
346
|
-
TableName: "
|
|
347
|
-
ExclusiveStartKey:
|
|
348
|
-
})
|
|
414
|
+
TableName: "Users",
|
|
415
|
+
ExclusiveStartKey: page2.LastEvaluatedKey,
|
|
416
|
+
})
|
|
349
417
|
);
|
|
350
|
-
|
|
418
|
+
expect(page3.LastEvaluatedKey).toBeUndefined(); // No more pages
|
|
419
|
+
```
|
|
351
420
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
421
|
+
#### S3 Pagination
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
425
|
+
|
|
426
|
+
const objects = Array.from({ length: 100 }, (_, i) => ({
|
|
355
427
|
Key: `file-${i + 1}.txt`,
|
|
428
|
+
Size: 1024,
|
|
429
|
+
LastModified: new Date(),
|
|
356
430
|
}));
|
|
357
431
|
|
|
358
432
|
s3Mock.on(ListObjectsV2Command).resolvesPaginated(objects, {
|
|
359
|
-
pageSize:
|
|
433
|
+
pageSize: 50,
|
|
360
434
|
itemsKey: "Contents",
|
|
361
|
-
tokenKey: "NextContinuationToken",
|
|
435
|
+
tokenKey: "NextContinuationToken",
|
|
436
|
+
inputTokenKey: "ContinuationToken"
|
|
362
437
|
});
|
|
438
|
+
|
|
439
|
+
// First page
|
|
440
|
+
const page1 = await client.send(
|
|
441
|
+
new ListObjectsV2Command({ Bucket: "my-bucket" })
|
|
442
|
+
);
|
|
443
|
+
expect(page1.Contents).toHaveLength(50);
|
|
444
|
+
// NextContinuationToken is the last object from page 1
|
|
445
|
+
expect(page1.NextContinuationToken).toEqual({ Key: "file-50.txt", ... });
|
|
446
|
+
|
|
447
|
+
// Second page
|
|
448
|
+
const page2 = await client.send(
|
|
449
|
+
new ListObjectsV2Command({
|
|
450
|
+
Bucket: "my-bucket",
|
|
451
|
+
ContinuationToken: page1.NextContinuationToken,
|
|
452
|
+
})
|
|
453
|
+
);
|
|
454
|
+
expect(page2.Contents).toHaveLength(50);
|
|
455
|
+
expect(page2.NextContinuationToken).toBeUndefined(); // No more pages
|
|
363
456
|
```
|
|
364
457
|
|
|
365
458
|
**Pagination Options:**
|
|
@@ -367,7 +460,20 @@ s3Mock.on(ListObjectsV2Command).resolvesPaginated(objects, {
|
|
|
367
460
|
- `pageSize` - Number of items per page (default: 10)
|
|
368
461
|
- `itemsKey` - Property name for items array in response (default: "Items")
|
|
369
462
|
- `tokenKey` - Property name for pagination token in response (default: "NextToken")
|
|
370
|
-
-
|
|
463
|
+
- DynamoDB: use `"LastEvaluatedKey"`
|
|
464
|
+
- S3: use `"NextContinuationToken"`
|
|
465
|
+
- `inputTokenKey` - Property name for pagination token in request (defaults to same as tokenKey)
|
|
466
|
+
- DynamoDB: use `"ExclusiveStartKey"`
|
|
467
|
+
- S3: use `"ContinuationToken"`
|
|
468
|
+
|
|
469
|
+
**How It Works:**
|
|
470
|
+
|
|
471
|
+
The mock automatically uses the **last item from each page** as the pagination token. This means:
|
|
472
|
+
|
|
473
|
+
- ✅ For DynamoDB: `LastEvaluatedKey` is a proper object (can be unmarshalled)
|
|
474
|
+
- ✅ For S3: `NextContinuationToken` is the last object
|
|
475
|
+
- ✅ Tokens represent actual data, not opaque strings
|
|
476
|
+
- ✅ Works correctly with `unmarshall()` for DynamoDB
|
|
371
477
|
- Use this when AWS service uses different names for input/output tokens (e.g., DynamoDB's `ExclusiveStartKey` vs `LastEvaluatedKey`)
|
|
372
478
|
|
|
373
479
|
### AWS Error Simulation
|
|
@@ -415,9 +521,7 @@ s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/response.txt");
|
|
|
415
521
|
|
|
416
522
|
### Debug Mode
|
|
417
523
|
|
|
418
|
-
Enable debug logging to
|
|
419
|
-
|
|
420
|
-
Enable debug logging to troubleshoot mock configurations and see detailed information about command matching:
|
|
524
|
+
Enable debug logging to see detailed information about mock configuration, lifecycle events, and command interactions:
|
|
421
525
|
|
|
422
526
|
```typescript
|
|
423
527
|
const s3Mock = mockClient(S3Client);
|
|
@@ -425,29 +529,102 @@ const s3Mock = mockClient(S3Client);
|
|
|
425
529
|
// Enable debug logging
|
|
426
530
|
s3Mock.enableDebug();
|
|
427
531
|
|
|
532
|
+
// Configuration logs appear immediately:
|
|
533
|
+
// [aws-sdk-vitest-mock](Debug) Configured resolves for GetObjectCommand
|
|
534
|
+
// {
|
|
535
|
+
// "matcher": {
|
|
536
|
+
// "Bucket": "test-bucket"
|
|
537
|
+
// },
|
|
538
|
+
// "strict": false
|
|
539
|
+
// }
|
|
428
540
|
s3Mock
|
|
429
541
|
.on(GetObjectCommand, { Bucket: "test-bucket" })
|
|
430
542
|
.resolves({ Body: "data" });
|
|
431
543
|
|
|
432
|
-
//
|
|
433
|
-
// [
|
|
434
|
-
//
|
|
435
|
-
//
|
|
544
|
+
// Interaction logs appear when commands are sent:
|
|
545
|
+
// [aws-sdk-vitest-mock](Debug) Received command: GetObjectCommand
|
|
546
|
+
// {
|
|
547
|
+
// "Bucket": "test-bucket",
|
|
548
|
+
// "Key": "file.txt"
|
|
549
|
+
// }
|
|
550
|
+
// [aws-sdk-vitest-mock](Debug) Found 1 mock(s) for GetObjectCommand
|
|
551
|
+
// [aws-sdk-vitest-mock](Debug) Using mock at index 0 for GetObjectCommand
|
|
436
552
|
await client.send(
|
|
437
553
|
new GetObjectCommand({ Bucket: "test-bucket", Key: "file.txt" }),
|
|
438
554
|
);
|
|
439
555
|
|
|
556
|
+
// Lifecycle logs:
|
|
557
|
+
// [aws-sdk-vitest-mock](Debug) Clearing call history (mocks preserved)
|
|
558
|
+
s3Mock.reset();
|
|
559
|
+
|
|
560
|
+
// [aws-sdk-vitest-mock](Debug) Restoring original client behavior and clearing all mocks
|
|
561
|
+
s3Mock.restore();
|
|
562
|
+
|
|
440
563
|
// Disable debug logging
|
|
441
564
|
s3Mock.disableDebug();
|
|
442
565
|
```
|
|
443
566
|
|
|
444
|
-
|
|
567
|
+
#### Global Debug Configuration
|
|
568
|
+
|
|
569
|
+
Enable debug logging for all mocks globally, with the ability to override at the individual mock level:
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { setGlobalDebug, mockClient } from "aws-sdk-vitest-mock";
|
|
573
|
+
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
|
574
|
+
import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb";
|
|
575
|
+
|
|
576
|
+
// Enable debug for all mocks
|
|
577
|
+
setGlobalDebug(true);
|
|
578
|
+
|
|
579
|
+
// All mocks will inherit the global debug setting
|
|
580
|
+
const s3Mock = mockClient(S3Client);
|
|
581
|
+
const dynamoMock = mockClient(DynamoDBClient);
|
|
582
|
+
|
|
583
|
+
// Both mocks will log debug information
|
|
584
|
+
s3Mock.on(GetObjectCommand).resolves({ Body: "data" });
|
|
585
|
+
dynamoMock.on(GetItemCommand).resolves({ Item: { id: { S: "1" } } });
|
|
586
|
+
|
|
587
|
+
// Override global setting for a specific mock
|
|
588
|
+
s3Mock.disableDebug(); // This mock won't log, but dynamoMock still will
|
|
589
|
+
|
|
590
|
+
// Disable global debug
|
|
591
|
+
setGlobalDebug(false);
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
**Debug Priority (highest to lowest):**
|
|
595
|
+
|
|
596
|
+
1. Individual mock's `enableDebug()` or `disableDebug()` call (explicit override)
|
|
597
|
+
2. Global debug setting via `setGlobalDebug()`
|
|
598
|
+
3. Default: disabled
|
|
599
|
+
|
|
600
|
+
**Key behaviors:**
|
|
601
|
+
|
|
602
|
+
- When global debug is enabled, all new and existing mocks will log unless explicitly disabled
|
|
603
|
+
- Individual mock settings always take priority over global settings
|
|
604
|
+
- `reset()` preserves individual debug settings
|
|
605
|
+
- Global debug can be changed at any time and affects all mocks without explicit settings
|
|
606
|
+
|
|
607
|
+
Debug mode provides comprehensive logging for:
|
|
608
|
+
|
|
609
|
+
**Mock Configuration:**
|
|
610
|
+
|
|
611
|
+
- Mock setup with `.on()`, `.resolves()`, `.rejects()`, `.callsFake()`, etc.
|
|
612
|
+
- Matcher details and strict mode settings
|
|
613
|
+
- Paginated response configuration
|
|
614
|
+
- File-based fixture loading
|
|
615
|
+
|
|
616
|
+
**Mock Interactions:**
|
|
445
617
|
|
|
446
618
|
- Incoming commands and their inputs
|
|
447
619
|
- Number of configured mocks for each command
|
|
448
620
|
- Mock matching results and reasons for failures
|
|
449
621
|
- One-time mock removal notifications
|
|
450
622
|
|
|
623
|
+
**Lifecycle Events:**
|
|
624
|
+
|
|
625
|
+
- Reset operations (clearing call history)
|
|
626
|
+
- Restore operations (removing all mocks)
|
|
627
|
+
|
|
451
628
|
## 🧪 Test Coverage
|
|
452
629
|
|
|
453
630
|
The library includes comprehensive test suites covering all features:
|
|
@@ -513,6 +690,8 @@ test("should call DynamoDB", async () => {
|
|
|
513
690
|
|
|
514
691
|
## 📚 API Reference
|
|
515
692
|
|
|
693
|
+
> TypeScript documentation for this library can be found at [here](https://sudokar.github.io/aws-sdk-vitest-mock/)
|
|
694
|
+
|
|
516
695
|
### `mockClient<TClient>(ClientConstructor)`
|
|
517
696
|
|
|
518
697
|
Creates a mock for an AWS SDK client constructor.
|
|
@@ -525,14 +704,18 @@ Mocks an existing AWS SDK client instance.
|
|
|
525
704
|
|
|
526
705
|
**Returns:** `AwsClientStub<TClient>`
|
|
527
706
|
|
|
707
|
+
### Global Debug Functions
|
|
708
|
+
|
|
709
|
+
- `setGlobalDebug(enabled: boolean)` - Enable or disable debug logging globally for all mocks
|
|
710
|
+
|
|
528
711
|
### `AwsClientStub` Methods
|
|
529
712
|
|
|
530
713
|
- `on(Command, matcher?, options?)` - Configure mock for a command
|
|
531
|
-
- `reset()` - Clear
|
|
714
|
+
- `reset()` - Clear call history while preserving mock configurations
|
|
532
715
|
- `restore()` - Restore original client behavior
|
|
533
716
|
- `calls()` - Get call history
|
|
534
|
-
- `enableDebug()` - Enable debug logging for troubleshooting
|
|
535
|
-
- `disableDebug()` - Disable debug logging
|
|
717
|
+
- `enableDebug()` - Enable debug logging for troubleshooting (overrides global setting)
|
|
718
|
+
- `disableDebug()` - Disable debug logging (overrides global setting)
|
|
536
719
|
|
|
537
720
|
### `AwsCommandStub` Methods (Chainable)
|
|
538
721
|
|
package/index.cjs
CHANGED
|
@@ -1 +1,18 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("vitest"),D=require("./matchers-CNhdB_9q.cjs"),F=require("node:fs"),I=require("node:path"),M=require("node:stream");class d extends Error{constructor(t,o,i,n){super(t),this.name="AwsError",this.code=o,this.statusCode=i,this.retryable=n}}const T=e=>new d(e?`The specified key does not exist. Key: ${e}`:"The specified key does not exist.","NoSuchKey",404,!1),K=e=>new d(e?`The specified bucket does not exist. Bucket: ${e}`:"The specified bucket does not exist.","NoSuchBucket",404,!1),A=e=>new d(e?`Access Denied for resource: ${e}`:"Access Denied","AccessDenied",403,!1),B=e=>new d(e?`Requested resource not found: ${e}`:"Requested resource not found","ResourceNotFoundException",400,!1),q=()=>new d("The conditional request failed","ConditionalCheckFailedException",400,!1),J=()=>new d("Rate exceeded","Throttling",400,!0),_=()=>new d("We encountered an internal error. Please try again.","InternalServerError",500,!0),z=e=>{try{return JSON.stringify(e,void 0,2)}catch{return typeof e=="object"&&e!==null?"[Complex Object]":typeof e=="string"?e:typeof e=="number"||typeof e=="boolean"?String(e):"[Non-serializable value]"}},v=(e,t)=>{const o=D.colors.magenta("aws-sdk-vitest-mock(debug):");if(t===void 0)console.log(`${o} ${e}`);else{const i=z(t);console.log(`${o} ${e}
|
|
2
|
+
${i}`)}},L=(e=!1)=>({enabled:e,explicitlySet:!1,log(t,o){this.enabled&&v(t,o)},logDirect(t,o){v(t,o)}}),w=e=>{e.enabled=!0,e.explicitlySet=!0},$=e=>{e.enabled=!1,e.explicitlySet=!0},V=e=>{const t=I.resolve(e),o=F.readFileSync(t,"utf8");return e.endsWith(".json")?JSON.parse(o):o},G=(e,t={})=>{const{pageSize:o=10,tokenKey:i="NextToken",itemsKey:n="Items"}=t;if(e.length===0)return[{[n]:[]}];const s=[];for(let r=0;r<e.length;r+=o){const c=e.slice(r,r+o),l=r+o<e.length,u={[n]:c};if(l){const a=u,g=c[c.length-1];a[i]=g}s.push(u)}return s},U=()=>typeof process<"u"&&process.versions?.node?"node":typeof process<"u"&&process.versions?.bun?"bun":"browser",H=e=>{const t=typeof e=="string"?Buffer.from(e,"utf8"):Buffer.from(e);let o=!1;return new M.Readable({read(){o||(this.push(t),this.push(null),o=!0)}})},Q=e=>{let t;return typeof e=="string"?t=new TextEncoder().encode(e):e instanceof Buffer?t=new Uint8Array(e):t=e,new ReadableStream({start(o){o.enqueue(t),o.close()}})},S=e=>{const t=U();return t==="node"||t==="bun"?H(e):Q(e)};let N=!1;function X(e){N=e}function h(e){return e.explicitlySet?e.enabled:N}function C(e,t){return Object.keys(t).every(o=>{const i=t[o],n=e[o];return i&&typeof i=="object"&&!Array.isArray(i)?typeof n!="object"||n===null?!1:C(n,i):n===i})}function E(e,t){if(e===t)return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return e===t;const o=Object.keys(e),i=Object.keys(t);return o.length!==i.length?!1:i.every(n=>{if(!Object.prototype.hasOwnProperty.call(e,n))return!1;const s=e,r=t,c=s[n],l=r[n];return typeof c=="object"&&c!==null&&typeof l=="object"&&l!==null?E(c,l):c===l})}function Y(e,t,o){const i=t.map((s,r)=>{const c=s.matcher?JSON.stringify(s.matcher,void 0,2):"any input",l=s.strict?" (strict mode)":"";return` Mock #${r+1}: ${c}${l}`}).join(`
|
|
3
|
+
`),n=JSON.stringify(o,void 0,2);return new Error(`No matching mock found for ${e}.
|
|
4
|
+
|
|
5
|
+
Found ${t.length} mock(s) but none matched the input.
|
|
6
|
+
|
|
7
|
+
Configured mocks:
|
|
8
|
+
${i}
|
|
9
|
+
|
|
10
|
+
Received input:
|
|
11
|
+
${n}
|
|
12
|
+
|
|
13
|
+
Tip: Enable debug mode with enableDebug() for detailed matching information.`)}function Z(e,t){const o=JSON.stringify(t,void 0,2);return new Error(`No mock configured for command: ${e}.
|
|
14
|
+
|
|
15
|
+
Received input:
|
|
16
|
+
${o}
|
|
17
|
+
|
|
18
|
+
Did you forget to call mockClient.on(${e})?`)}function ee(e,t){return e.findIndex(o=>o.strict?o.matcher&&E(t,o.matcher):!o.matcher||C(t,o.matcher))}function x(e){return async function(t){const o=()=>this,i=h(e.debugLogger),n=t.constructor.name;i&&e.debugLogger.logDirect(`Received command: ${n}`,t.input);const s=e.map.get(t.constructor);if(!s)throw i&&e.debugLogger.logDirect(`No mocks configured for ${n}`),Z(n,t.input);i&&e.debugLogger.logDirect(`Found ${s.length} mock(s) for ${n}`);const r=ee(s,t.input);if(r===-1)throw i&&e.debugLogger.logDirect(`No matching mock found for ${n}`,t.input),Y(n,s,t.input);const c=s[r];if(!c)throw new Error(`Mock at index ${r} not found`);return i&&e.debugLogger.logDirect(`Using mock at index ${r} for ${n}`),c.once&&(s.splice(r,1),i&&e.debugLogger.logDirect(`Removed one-time mock for ${n}`)),c.handler(t.input,o())}}function R(e,t,o,i={}){const n=(r,c,l)=>{const u={matcher:o,handler:r,once:c,strict:!!i.strict},a=e.map.get(t)??[],g=h(e.debugLogger);if(c){const f=a.findIndex(p=>!p.once);f===-1?a.push(u):a.splice(f,0,u),e.map.set(t,a),g&&e.debugLogger.logDirect(`Configured ${l}Once for ${t.name}`,o?{matcher:o,strict:!!i.strict}:void 0)}else{const f=a.filter(p=>p.once||JSON.stringify(p.matcher)!==JSON.stringify(o));f.push(u),e.map.set(t,f),g&&e.debugLogger.logDirect(`Configured ${l} for ${t.name}`,o?{matcher:o,strict:!!i.strict}:void 0)}},s={resolves(r){return n(()=>Promise.resolve(r),!1,"resolves"),s},rejects(r){return n(()=>{const c=typeof r=="string"?new Error(r):r;return Promise.reject(c)},!1,"rejects"),s},callsFake(r){return n(r,!1,"callsFake"),s},resolvesOnce(r){return n(()=>Promise.resolve(r),!0,"resolves"),s},rejectsOnce(r){return n(()=>{const c=typeof r=="string"?new Error(r):r;return Promise.reject(c)},!0,"rejects"),s},callsFakeOnce(r){return n(r,!0,"callsFake"),s},resolvesStream(r){return n(()=>Promise.resolve({Body:S(r)}),!1,"resolvesStream"),s},resolvesStreamOnce(r){return n(()=>Promise.resolve({Body:S(r)}),!0,"resolvesStream"),s},resolvesWithDelay(r,c){const l=u=>{setTimeout(()=>u(r),c)};return n(()=>new Promise(l),!1,"resolvesWithDelay"),s},rejectsWithDelay(r,c){const l=typeof r=="string"?new Error(r):r,u=(a,g)=>{setTimeout(()=>g(l),c)};return n(()=>new Promise(u),!1,"rejectsWithDelay"),s},rejectsWithNoSuchKey(r){return n(()=>Promise.reject(T(r)),!1,"rejectsWithNoSuchKey"),s},rejectsWithNoSuchBucket(r){return n(()=>Promise.reject(K(r)),!1,"rejectsWithNoSuchBucket"),s},rejectsWithAccessDenied(r){return n(()=>Promise.reject(A(r)),!1,"rejectsWithAccessDenied"),s},rejectsWithResourceNotFound(r){return n(()=>Promise.reject(B(r)),!1,"rejectsWithResourceNotFound"),s},rejectsWithConditionalCheckFailed(){return n(()=>Promise.reject(q()),!1,"rejectsWithConditionalCheckFailed"),s},rejectsWithThrottling(){return n(()=>Promise.reject(J()),!1,"rejectsWithThrottling"),s},rejectsWithInternalServerError(){return n(()=>Promise.reject(_()),!1,"rejectsWithInternalServerError"),s},resolvesPaginated(r,c={}){const l=G(r,c);let u=0;return e.debugLogger.log(`Configured resolvesPaginated for ${t.name}`,{pageSize:c.pageSize,itemsCount:r.length}),n(a=>{const g=c.tokenKey||"NextToken",f=c.inputTokenKey||g,m=a[f];if(m!=null){const W=c.itemsKey||"Items";let k=0;for(const P of l){const b=P[W];if(b&&b.length>0){const O=b[b.length-1];if(JSON.stringify(O)===JSON.stringify(m)){u=k+1;break}}k++}}else u=0;const y=l[u]||l[l.length-1]||l[0];if(!y)throw new Error("No paginated responses available");return u=Math.min(u+1,l.length-1),Promise.resolve(y)},!1,"resolvesPaginated"),s},resolvesFromFile(r){return e.debugLogger.log(`Configured resolvesFromFile for ${t.name}`,{filePath:r}),n(()=>{const c=V(r);return Promise.resolve(c)},!1,"resolvesFromFile"),s}};return s}const te=e=>{const t={map:new WeakMap,debugLogger:L()},o=e.prototype,i=j.vi.spyOn(o,"send").mockImplementation(x(t));return{client:void 0,on:(s,r,c)=>R(t,s,r,c),reset:()=>{h(t.debugLogger)&&t.debugLogger.logDirect("Clearing call history (mocks preserved)"),i.mockClear()},restore:()=>{h(t.debugLogger)&&t.debugLogger.logDirect("Restoring original client behavior and clearing all mocks"),i.mockRestore(),t.map=new WeakMap},calls:()=>i.mock.calls.map(s=>s[0]),__rawCalls:()=>i.mock.calls,enableDebug:()=>{w(t.debugLogger)},disableDebug:()=>{$(t.debugLogger)}}},re=e=>{const t={map:new WeakMap,debugLogger:L()},o=j.vi.spyOn(e,"send").mockImplementation(x(t));return{client:e,on:(n,s,r)=>R(t,n,s,r),reset:()=>{h(t.debugLogger)&&t.debugLogger.logDirect("Clearing call history (mocks preserved) for client instance"),o.mockClear()},restore:()=>{h(t.debugLogger)&&t.debugLogger.logDirect("Restoring original client behavior and clearing all mocks for client instance"),o.mockRestore(),t.map=new WeakMap},calls:()=>o.mock.calls.map(n=>n[0]),__rawCalls:()=>o.mock.calls,enableDebug:()=>{w(t.debugLogger)},disableDebug:()=>{$(t.debugLogger)}}};exports.matchers=D.matchers;exports.mockClient=te;exports.mockClientInstance=re;exports.setGlobalDebug=X;
|
package/index.d.ts
CHANGED
|
@@ -1,2 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AWS SDK Vitest Mock - A powerful, type-safe mocking library for AWS SDK v3 with Vitest
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*
|
|
6
|
+
* @example Basic Setup
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { mockClient } from 'aws-sdk-vitest-mock';
|
|
9
|
+
* import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
10
|
+
*
|
|
11
|
+
* const s3Mock = mockClient(S3Client);
|
|
12
|
+
* s3Mock.on(GetObjectCommand).resolves({ Body: 'file contents' });
|
|
13
|
+
*
|
|
14
|
+
* const client = new S3Client({});
|
|
15
|
+
* const result = await client.send(new GetObjectCommand({ Bucket: 'test', Key: 'file.txt' }));
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example Using Matchers
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { expect } from 'vitest';
|
|
21
|
+
* import { matchers } from 'aws-sdk-vitest-mock';
|
|
22
|
+
*
|
|
23
|
+
* expect.extend(matchers);
|
|
24
|
+
*
|
|
25
|
+
* expect(s3Mock).toHaveReceivedCommand(GetObjectCommand);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Core Functions for mocking AWS SDK clients
|
|
30
|
+
* @category Core Functions
|
|
31
|
+
*/
|
|
32
|
+
export { mockClient, mockClientInstance, setGlobalDebug, } from './lib/mock-client.js';
|
|
33
|
+
/**
|
|
34
|
+
* Command stub interface for configuring mock behaviors
|
|
35
|
+
* @category Command Stub
|
|
36
|
+
*/
|
|
37
|
+
export type { AwsCommandStub, AwsClientStub } from './lib/mock-client.js';
|
|
38
|
+
/**
|
|
39
|
+
* Custom Vitest matchers for AWS SDK assertions
|
|
40
|
+
* @category Matchers
|
|
41
|
+
*/
|
|
2
42
|
export { matchers } from './lib/matchers.js';
|
|
43
|
+
/**
|
|
44
|
+
* TypeScript types for matcher interfaces
|
|
45
|
+
* @category Matchers
|
|
46
|
+
*/
|
|
47
|
+
export type { AwsSdkMatchers } from './lib/matchers.js';
|