aws-sdk-vitest-mock 0.4.0 → 0.5.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 +177 -40
- package/index.cjs +18 -1
- package/index.d.ts +46 -1
- package/index.js +329 -222
- package/lib/matchers.d.ts +141 -0
- package/lib/mock-client.d.ts +436 -19
- package/lib/utils/paginator-helpers.d.ts +84 -0
- package/{matchers-C6AtmwWz.js → matchers-ClGOsQx8.js} +99 -0
- package/{matchers-Rq18z2C7.cjs → matchers-Dkkl4vtx.cjs} +1 -1
- 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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
373
|
+
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
|
|
374
|
+
import { DynamoDBClient, ScanCommand } from '@aws-sdk/client-dynamodb';
|
|
375
|
+
|
|
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
|
+
];
|
|
322
382
|
|
|
323
|
-
|
|
324
|
-
|
|
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", ... }));
|
|
398
|
+
|
|
399
|
+
// Unmarshall the items
|
|
400
|
+
const page1Users = page1.Items.map(item => unmarshall(item));
|
|
401
|
+
console.log(page1Users[0]); // { id: "user-1", name: "Alice", ... }
|
|
333
402
|
|
|
334
|
-
//
|
|
335
|
-
const
|
|
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
|
+
```
|
|
420
|
+
|
|
421
|
+
#### S3 Pagination
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
351
425
|
|
|
352
|
-
|
|
353
|
-
// S3 uses the same key for input and output, so inputTokenKey is optional
|
|
354
|
-
const objects = Array.from({ length: 15 }, (_, i) => ({
|
|
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,62 @@ 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
|
-
Debug mode
|
|
567
|
+
Debug mode provides comprehensive logging for:
|
|
568
|
+
|
|
569
|
+
**Mock Configuration:**
|
|
570
|
+
|
|
571
|
+
- Mock setup with `.on()`, `.resolves()`, `.rejects()`, `.callsFake()`, etc.
|
|
572
|
+
- Matcher details and strict mode settings
|
|
573
|
+
- Paginated response configuration
|
|
574
|
+
- File-based fixture loading
|
|
575
|
+
|
|
576
|
+
**Mock Interactions:**
|
|
445
577
|
|
|
446
578
|
- Incoming commands and their inputs
|
|
447
579
|
- Number of configured mocks for each command
|
|
448
580
|
- Mock matching results and reasons for failures
|
|
449
581
|
- One-time mock removal notifications
|
|
450
582
|
|
|
583
|
+
**Lifecycle Events:**
|
|
584
|
+
|
|
585
|
+
- Reset operations (clearing call history)
|
|
586
|
+
- Restore operations (removing all mocks)
|
|
587
|
+
|
|
451
588
|
## 🧪 Test Coverage
|
|
452
589
|
|
|
453
590
|
The library includes comprehensive test suites covering all features:
|
|
@@ -528,7 +665,7 @@ Mocks an existing AWS SDK client instance.
|
|
|
528
665
|
### `AwsClientStub` Methods
|
|
529
666
|
|
|
530
667
|
- `on(Command, matcher?, options?)` - Configure mock for a command
|
|
531
|
-
- `reset()` - Clear
|
|
668
|
+
- `reset()` - Clear call history while preserving mock configurations
|
|
532
669
|
- `restore()` - Restore original client behavior
|
|
533
670
|
- `calls()` - Get call history
|
|
534
671
|
- `enableDebug()` - Enable debug logging for troubleshooting
|
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 v=require("vitest"),m=require("./matchers-Dkkl4vtx.cjs"),x=require("node:fs"),O=require("node:path"),F=require("node:stream");class f extends Error{constructor(t,o,i,n){super(t),this.name="AwsError",this.code=o,this.statusCode=i,this.retryable=n}}const L=e=>{const t=e?`The specified key does not exist. Key: ${e}`:"The specified key does not exist.";return new f(t,"NoSuchKey",404,!1)},D=e=>{const t=e?`The specified bucket does not exist. Bucket: ${e}`:"The specified bucket does not exist.";return new f(t,"NoSuchBucket",404,!1)},I=e=>{const t=e?`Access Denied for resource: ${e}`:"Access Denied";return new f(t,"AccessDenied",403,!1)},T=e=>{const t=e?`Requested resource not found: ${e}`:"Requested resource not found";return new f(t,"ResourceNotFoundException",400,!1)},K=()=>new f("The conditional request failed","ConditionalCheckFailedException",400,!1),M=()=>new f("Rate exceeded","Throttling",400,!0),A=()=>new f("We encountered an internal error. Please try again.","InternalServerError",500,!0);function B(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]"}}function j(){return{enabled:!1,log(e,t){if(this.enabled)if(t===void 0)console.log(`${m.colors.magenta("aws-sdk-vitest-mock(debug):")} ${e}`);else{const o=B(t);console.log(`${m.colors.magenta("aws-sdk-vitest-mock(debug):")} ${e}
|
|
2
|
+
${o}`)}}}}function S(e){e.enabled=!0}function w(e){e.enabled=!1}function q(e){const t=O.resolve(e),o=x.readFileSync(t,"utf8");return e.endsWith(".json")?JSON.parse(o):o}function J(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),u=r+o<e.length,l={[n]:c};if(u){const g=l,a=c[c.length-1];g[i]=a}s.push(l)}return s}function _(){return typeof process<"u"&&process.versions?.node?"node":typeof process<"u"&&process.versions?.bun?"bun":"browser"}function z(e){const t=typeof e=="string"?Buffer.from(e,"utf8"):Buffer.from(e);let o=!1;return new F.Readable({read(){o||(this.push(t),this.push(null),o=!0)}})}function V(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()}})}function k(e){const t=_();return t==="node"||t==="bun"?z(e):V(e)}function $(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:$(n,i):n===i})}function C(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],u=r[n];return typeof c=="object"&&c!==null&&typeof u=="object"&&u!==null?C(c,u):c===u})}function N(e){return async function(t){const o=()=>this;e.debugLogger.log(`Received command: ${t.constructor.name}`,t.input);const i=e.map.get(t.constructor);if(i){e.debugLogger.log(`Found ${i.length} mock(s) for ${t.constructor.name}`);const s=i.findIndex(r=>r.strict?r.matcher&&C(t.input,r.matcher):!r.matcher||$(t.input,r.matcher));if(s===-1){e.debugLogger.log(`No matching mock found for ${t.constructor.name}`,t.input);const r=i.map((u,l)=>{const g=u.matcher?JSON.stringify(u.matcher,void 0,2):"any input",a=u.strict?" (strict mode)":"";return` Mock #${l+1}: ${g}${a}`}).join(`
|
|
3
|
+
`),c=JSON.stringify(t.input,void 0,2);throw new Error(`No matching mock found for ${t.constructor.name}.
|
|
4
|
+
|
|
5
|
+
Found ${i.length} mock(s) but none matched the input.
|
|
6
|
+
|
|
7
|
+
Configured mocks:
|
|
8
|
+
${r}
|
|
9
|
+
|
|
10
|
+
Received input:
|
|
11
|
+
${c}
|
|
12
|
+
|
|
13
|
+
Tip: Enable debug mode with enableDebug() for detailed matching information.`)}else{const r=i[s];if(!r)throw new Error(`Mock at index ${s} not found`);return e.debugLogger.log(`Using mock at index ${s} for ${t.constructor.name}`),r.once&&(i.splice(s,1),e.debugLogger.log(`Removed one-time mock for ${t.constructor.name}`)),r.handler(t.input,o())}}else e.debugLogger.log(`No mocks configured for ${t.constructor.name}`);const n=JSON.stringify(t.input,void 0,2);throw new Error(`No mock configured for command: ${t.constructor.name}.
|
|
14
|
+
|
|
15
|
+
Received input:
|
|
16
|
+
${n}
|
|
17
|
+
|
|
18
|
+
Did you forget to call mockClient.on(${t.constructor.name})?`)}}function R(e,t,o,i={}){const n=(r,c,u)=>{const l={matcher:o,handler:r,once:c,strict:!!i.strict},g=e.map.get(t)??[];if(c){const a=g.findIndex(d=>!d.once);a===-1?g.push(l):g.splice(a,0,l),e.map.set(t,g),e.debugLogger.log(`Configured ${u}Once for ${t.name}`,o?{matcher:o,strict:!!i.strict}:void 0)}else{const a=g.filter(d=>d.once||JSON.stringify(d.matcher)!==JSON.stringify(o));a.push(l),e.map.set(t,a),e.debugLogger.log(`Configured ${u} 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:k(r)}),!1,"resolvesStream"),s},resolvesStreamOnce(r){return n(()=>Promise.resolve({Body:k(r)}),!0,"resolvesStream"),s},resolvesWithDelay(r,c){const u=l=>{setTimeout(()=>l(r),c)};return n(()=>new Promise(u),!1,"resolvesWithDelay"),s},rejectsWithDelay(r,c){const u=typeof r=="string"?new Error(r):r,l=(g,a)=>{setTimeout(()=>a(u),c)};return n(()=>new Promise(l),!1,"rejectsWithDelay"),s},rejectsWithNoSuchKey(r){return n(()=>Promise.reject(L(r)),!1,"rejectsWithNoSuchKey"),s},rejectsWithNoSuchBucket(r){return n(()=>Promise.reject(D(r)),!1,"rejectsWithNoSuchBucket"),s},rejectsWithAccessDenied(r){return n(()=>Promise.reject(I(r)),!1,"rejectsWithAccessDenied"),s},rejectsWithResourceNotFound(r){return n(()=>Promise.reject(T(r)),!1,"rejectsWithResourceNotFound"),s},rejectsWithConditionalCheckFailed(){return n(()=>Promise.reject(K()),!1,"rejectsWithConditionalCheckFailed"),s},rejectsWithThrottling(){return n(()=>Promise.reject(M()),!1,"rejectsWithThrottling"),s},rejectsWithInternalServerError(){return n(()=>Promise.reject(A()),!1,"rejectsWithInternalServerError"),s},resolvesPaginated(r,c={}){const u=J(r,c);let l=0;return e.debugLogger.log(`Configured resolvesPaginated for ${t.name}`,{pageSize:c.pageSize,itemsCount:r.length}),n(g=>{const a=c.tokenKey||"NextToken",d=c.inputTokenKey||a,h=g[d];if(h!=null){const E=c.itemsKey||"Items";let y=0;for(const W of u){const p=W[E];if(p&&p.length>0){const P=p[p.length-1];if(JSON.stringify(P)===JSON.stringify(h)){l=y+1;break}}y++}}else l=0;const b=u[l]||u[u.length-1]||u[0];if(!b)throw new Error("No paginated responses available");return l=Math.min(l+1,u.length-1),Promise.resolve(b)},!1,"resolvesPaginated"),s},resolvesFromFile(r){return e.debugLogger.log(`Configured resolvesFromFile for ${t.name}`,{filePath:r}),n(()=>{const c=q(r);return Promise.resolve(c)},!1,"resolvesFromFile"),s}};return s}const U=e=>{const t={map:new WeakMap,debugLogger:j()},o=e.prototype,i=v.vi.spyOn(o,"send").mockImplementation(N(t));return{client:void 0,on:(s,r,c)=>R(t,s,r,c),reset:()=>{t.debugLogger.log("Clearing call history (mocks preserved)"),i.mockClear()},restore:()=>{t.debugLogger.log("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:()=>{S(t.debugLogger)},disableDebug:()=>{w(t.debugLogger)}}},G=e=>{const t={map:new WeakMap,debugLogger:j()},o=v.vi.spyOn(e,"send").mockImplementation(N(t));return{client:e,on:(n,s,r)=>R(t,n,s,r),reset:()=>{t.debugLogger.log("Clearing call history (mocks preserved) for client instance"),o.mockClear()},restore:()=>{t.debugLogger.log("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:()=>{S(t.debugLogger)},disableDebug:()=>{w(t.debugLogger)}}};exports.matchers=m.matchers;exports.mockClient=U;exports.mockClientInstance=G;
|
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 } 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';
|