aws-sdk-vitest-mock 0.1.0 → 0.2.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/README.md CHANGED
@@ -44,31 +44,53 @@ pnpm add -D aws-sdk-vitest-mock
44
44
  ### Basic Usage
45
45
 
46
46
  ```typescript
47
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
47
48
  import { mockClient } from "aws-sdk-vitest-mock";
48
49
  import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
49
50
 
50
- // Mock the S3 client
51
- const s3Mock = mockClient(S3Client);
51
+ // Your application code
52
+ class DocumentService {
53
+ constructor(private s3Client: S3Client) {}
54
+
55
+ async getDocument(bucket: string, key: string) {
56
+ const result = await this.s3Client.send(
57
+ new GetObjectCommand({ Bucket: bucket, Key: key }),
58
+ );
59
+ return result.Body;
60
+ }
61
+ }
62
+
63
+ describe("DocumentService", () => {
64
+ let s3Mock: ReturnType<typeof mockClient>;
65
+ let documentService: DocumentService;
66
+
67
+ beforeEach(() => {
68
+ // Mock the S3 client
69
+ s3Mock = mockClient(S3Client);
70
+
71
+ // Create service with real S3Client (which is now mocked)
72
+ const s3Client = new S3Client({ region: "us-east-1" });
73
+ documentService = new DocumentService(s3Client);
74
+ });
52
75
 
53
- // Configure mock responses
54
- s3Mock.on(GetObjectCommand).resolves({
55
- Body: "mock data",
56
- ContentType: "text/plain",
57
- });
76
+ afterEach(() => {
77
+ s3Mock.restore();
78
+ });
58
79
 
59
- // Use in your tests
60
- const client = new S3Client({});
61
- const result = await client.send(
62
- new GetObjectCommand({
63
- Bucket: "my-bucket",
64
- Key: "my-key",
65
- }),
66
- );
80
+ test("should retrieve document from S3", async () => {
81
+ // Configure mock response
82
+ s3Mock.on(GetObjectCommand).resolves({
83
+ Body: "document content",
84
+ ContentType: "text/plain",
85
+ });
67
86
 
68
- console.log(result.Body); // 'mock data'
87
+ // Test your application code
88
+ const result = await documentService.getDocument("my-bucket", "doc.txt");
69
89
 
70
- // Clean up
71
- s3Mock.restore();
90
+ expect(result).toBe("document content");
91
+ expect(s3Mock).toHaveReceivedCommand(GetObjectCommand);
92
+ });
93
+ });
72
94
  ```
73
95
 
74
96
  ### Request Matching
@@ -99,6 +121,199 @@ s3Mock
99
121
  // All other calls return 'subsequent calls'
100
122
  ```
101
123
 
124
+ ### Fixture Loading
125
+
126
+ Load mock responses from files for easier test data management:
127
+
128
+ ```typescript
129
+ // Load JSON response from file
130
+ s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/s3-response.json");
131
+
132
+ // Load text response from file
133
+ s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/response.txt");
134
+
135
+ // JSON files are automatically parsed, text files returned as strings
136
+ // File paths are resolved relative to current working directory
137
+ ```
138
+
139
+ ### Paginator Support
140
+
141
+ Mock AWS SDK v3 pagination with automatic token handling:
142
+
143
+ ```typescript
144
+ // Mock DynamoDB scan with pagination
145
+ const items = Array.from({ length: 25 }, (_, i) => ({
146
+ id: { S: `item-${i + 1}` },
147
+ }));
148
+
149
+ dynamoMock.on(ScanCommand).resolvesPaginated(items, {
150
+ pageSize: 10,
151
+ itemsKey: "Items",
152
+ tokenKey: "NextToken",
153
+ });
154
+
155
+ // First call returns items 1-10 with NextToken
156
+ // Second call with NextToken returns items 11-20
157
+ // Third call returns items 21-25 without NextToken
158
+
159
+ // Mock S3 list objects with pagination
160
+ const objects = Array.from({ length: 15 }, (_, i) => ({
161
+ Key: `file-${i + 1}.txt`,
162
+ }));
163
+
164
+ s3Mock.on(ListObjectsV2Command).resolvesPaginated(objects, {
165
+ pageSize: 10,
166
+ itemsKey: "Contents",
167
+ tokenKey: "ContinuationToken",
168
+ });
169
+ ```
170
+
171
+ ### DynamoDB with Marshal/Unmarshal
172
+
173
+ Mock DynamoDB operations using AWS SDK's marshal/unmarshal utilities for type-safe data handling:
174
+
175
+ ```typescript
176
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
177
+ import { mockClient } from "aws-sdk-vitest-mock";
178
+ import {
179
+ DynamoDBClient,
180
+ GetItemCommand,
181
+ PutItemCommand,
182
+ } from "@aws-sdk/client-dynamodb";
183
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
184
+
185
+ // Your application service
186
+ class UserService {
187
+ constructor(private dynamoClient: DynamoDBClient) {}
188
+
189
+ async getUser(userId: string) {
190
+ const result = await this.dynamoClient.send(
191
+ new GetItemCommand({
192
+ TableName: "Users",
193
+ Key: marshall({ id: userId }),
194
+ }),
195
+ );
196
+
197
+ return result.Item ? unmarshall(result.Item) : null;
198
+ }
199
+
200
+ async createUser(user: { id: string; name: string; email: string }) {
201
+ await this.dynamoClient.send(
202
+ new PutItemCommand({
203
+ TableName: "Users",
204
+ Item: marshall(user),
205
+ }),
206
+ );
207
+ }
208
+ }
209
+
210
+ describe("UserService with DynamoDB", () => {
211
+ let dynamoMock: ReturnType<typeof mockClient>;
212
+ let userService: UserService;
213
+
214
+ beforeEach(() => {
215
+ dynamoMock = mockClient(DynamoDBClient);
216
+ const dynamoClient = new DynamoDBClient({ region: "us-east-1" });
217
+ userService = new UserService(dynamoClient);
218
+ });
219
+
220
+ afterEach(() => {
221
+ dynamoMock.restore();
222
+ });
223
+
224
+ test("should get user by id", async () => {
225
+ const mockUser = { id: "123", name: "John Doe", email: "john@example.com" };
226
+
227
+ // Mock DynamoDB response with marshalled data
228
+ dynamoMock.on(GetItemCommand).resolves({
229
+ Item: marshall(mockUser),
230
+ });
231
+
232
+ const result = await userService.getUser("123");
233
+
234
+ expect(result).toEqual(mockUser);
235
+ expect(dynamoMock).toHaveReceivedCommandWith(GetItemCommand, {
236
+ TableName: "Users",
237
+ Key: marshall({ id: "123" }),
238
+ });
239
+ });
240
+
241
+ test("should create new user", async () => {
242
+ const newUser = {
243
+ id: "456",
244
+ name: "Jane Smith",
245
+ email: "jane@example.com",
246
+ };
247
+
248
+ dynamoMock.on(PutItemCommand).resolves({});
249
+
250
+ await userService.createUser(newUser);
251
+
252
+ expect(dynamoMock).toHaveReceivedCommandWith(PutItemCommand, {
253
+ TableName: "Users",
254
+ Item: marshall(newUser),
255
+ });
256
+ });
257
+
258
+ test("should return null for non-existent user", async () => {
259
+ dynamoMock.on(GetItemCommand).resolves({}); // No Item in response
260
+
261
+ const result = await userService.getUser("999");
262
+
263
+ expect(result).toBeNull();
264
+ });
265
+ });
266
+ ```
267
+
268
+ ### Stream Mocking (S3 Helper)
269
+
270
+ Mock S3 operations that return streams with automatic environment detection:
271
+
272
+ ```typescript
273
+ // Mock with string content
274
+ s3Mock.on(GetObjectCommand).resolvesStream("Hello, World!");
275
+
276
+ // Mock with Buffer
277
+ s3Mock.on(GetObjectCommand).resolvesStream(Buffer.from("Binary data"));
278
+
279
+ // One-time stream response
280
+ s3Mock
281
+ .on(GetObjectCommand)
282
+ .resolvesStreamOnce("First call")
283
+ .resolvesStream("Subsequent calls");
284
+ ```
285
+
286
+ ### Delay/Latency Simulation
287
+
288
+ Simulate network delays for testing timeouts and race conditions:
289
+
290
+ ```typescript
291
+ // Resolve with delay
292
+ s3Mock.on(GetObjectCommand).resolvesWithDelay({ Body: "data" }, 1000);
293
+
294
+ // Reject with delay
295
+ s3Mock.on(GetObjectCommand).rejectsWithDelay("Network timeout", 500);
296
+ ```
297
+
298
+ ### AWS Error Simulation
299
+
300
+ Convenient methods for common AWS errors:
301
+
302
+ ```typescript
303
+ // S3 Errors
304
+ s3Mock.on(GetObjectCommand).rejectsWithNoSuchKey("missing-key");
305
+ s3Mock.on(GetObjectCommand).rejectsWithNoSuchBucket("missing-bucket");
306
+ s3Mock.on(GetObjectCommand).rejectsWithAccessDenied("protected-resource");
307
+
308
+ // DynamoDB Errors
309
+ dynamoMock.on(GetItemCommand).rejectsWithResourceNotFound("missing-table");
310
+ dynamoMock.on(PutItemCommand).rejectsWithConditionalCheckFailed();
311
+
312
+ // General AWS Errors
313
+ s3Mock.on(GetObjectCommand).rejectsWithThrottling();
314
+ s3Mock.on(GetObjectCommand).rejectsWithInternalServerError();
315
+ ```
316
+
102
317
  ### Error Handling
103
318
 
104
319
  ```typescript
@@ -124,20 +339,80 @@ s3Mock.on(GetObjectCommand).callsFake(async (input, getClient) => {
124
339
  ### Mocking Existing Instances
125
340
 
126
341
  ```typescript
127
- const existingClient = new S3Client({ region: "us-east-1" });
128
- const mock = mockClientInstance(existingClient);
342
+ // Your application service that uses an injected S3 client
343
+ class FileUploadService {
344
+ constructor(private s3Client: S3Client) {}
345
+
346
+ async uploadFile(bucket: string, key: string, data: string) {
347
+ return await this.s3Client.send(
348
+ new PutObjectCommand({ Bucket: bucket, Key: key, Body: data }),
349
+ );
350
+ }
351
+ }
352
+
353
+ test("should mock existing S3 client instance", async () => {
354
+ // Create the client your application will use
355
+ const s3Client = new S3Client({ region: "us-east-1" });
356
+ const service = new FileUploadService(s3Client);
357
+
358
+ // Mock the existing client instance
359
+ const mock = mockClientInstance(s3Client);
360
+ mock.on(PutObjectCommand).resolves({ ETag: "mock-etag" });
361
+
362
+ // Test your service
363
+ const result = await service.uploadFile("bucket", "key", "data");
364
+
365
+ expect(result.ETag).toBe("mock-etag");
366
+ expect(mock).toHaveReceivedCommand(PutObjectCommand);
367
+ });
368
+ ```
369
+
370
+ ### Debug Mode
129
371
 
130
- mock.on(GetObjectCommand).resolves({ Body: "mocked" });
372
+ Enable debug logging to troubleshoot mock configurations and see detailed information about command matching:
131
373
 
132
- // The existing instance is now mocked
133
- const result = await existingClient.send(
134
- new GetObjectCommand({
135
- Bucket: "b",
136
- Key: "k",
137
- }),
374
+ ```typescript
375
+ const s3Mock = mockClient(S3Client);
376
+
377
+ // Enable debug logging
378
+ s3Mock.enableDebug();
379
+
380
+ s3Mock
381
+ .on(GetObjectCommand, { Bucket: "test-bucket" })
382
+ .resolves({ Body: "data" });
383
+
384
+ // This will log:
385
+ // [AWS Mock Debug] Received command: GetObjectCommand
386
+ // [AWS Mock Debug] Found 1 mock(s) for GetObjectCommand
387
+ // [AWS Mock Debug] Using mock at index 0 for GetObjectCommand
388
+ await client.send(
389
+ new GetObjectCommand({ Bucket: "test-bucket", Key: "file.txt" }),
138
390
  );
391
+
392
+ // Disable debug logging
393
+ s3Mock.disableDebug();
139
394
  ```
140
395
 
396
+ Debug mode logs include:
397
+
398
+ - Incoming commands and their inputs
399
+ - Number of configured mocks for each command
400
+ - Mock matching results and reasons for failures
401
+ - One-time mock removal notifications
402
+
403
+ ## 🧪 Test Coverage
404
+
405
+ The library includes comprehensive test suites covering all features:
406
+
407
+ - **Core mocking functionality** - Command matching, response handling, sequential responses
408
+ - **Paginator support** - Automatic token handling for AWS pagination patterns
409
+ - **Debug logging** - Enable/disable functionality and proper console output formatting
410
+ - **Stream mocking** - S3 stream responses with environment detection
411
+ - **Error simulation** - AWS-specific errors and general error handling
412
+ - **Custom matchers** - Vitest integration for asserting command calls
413
+
414
+ All utilities have dedicated test files ensuring reliability and maintainability.
415
+
141
416
  ## 🧪 Custom Matchers
142
417
 
143
418
  Import the custom matchers in your test setup:
@@ -182,6 +457,9 @@ test("should call DynamoDB", async () => {
182
457
  expect(ddbMock).toHaveReceivedNthCommandWith(1, GetItemCommand, {
183
458
  TableName: "users",
184
459
  });
460
+
461
+ // Assert no other commands were received
462
+ expect(ddbMock).toHaveReceivedNoOtherCommands([GetItemCommand]);
185
463
  });
186
464
  ```
187
465
 
@@ -205,6 +483,8 @@ Mocks an existing AWS SDK client instance.
205
483
  - `reset()` - Clear all mocks and call history
206
484
  - `restore()` - Restore original client behavior
207
485
  - `calls()` - Get call history
486
+ - `enableDebug()` - Enable debug logging for troubleshooting
487
+ - `disableDebug()` - Disable debug logging
208
488
 
209
489
  ### `AwsCommandStub` Methods (Chainable)
210
490
 
@@ -214,6 +494,19 @@ Mocks an existing AWS SDK client instance.
214
494
  - `rejectsOnce(error)` - Return error once
215
495
  - `callsFake(handler)` - Custom response handler
216
496
  - `callsFakeOnce(handler)` - Custom response handler (once)
497
+ - `resolvesStream(data)` - Return stream response (S3 helper)
498
+ - `resolvesStreamOnce(data)` - Return stream response once (S3 helper)
499
+ - `resolvesWithDelay(output, delayMs)` - Return response with delay
500
+ - `rejectsWithDelay(error, delayMs)` - Return error with delay
501
+ - `resolvesPaginated(items, options?)` - Return paginated responses with automatic token handling
502
+ - `resolvesFromFile(filePath)` - Load response from file (JSON files are parsed, others returned as strings)
503
+ - `rejectsWithNoSuchKey(key?)` - Reject with S3 NoSuchKey error
504
+ - `rejectsWithNoSuchBucket(bucket?)` - Reject with S3 NoSuchBucket error
505
+ - `rejectsWithAccessDenied(resource?)` - Reject with AccessDenied error
506
+ - `rejectsWithResourceNotFound(resource?)` - Reject with DynamoDB ResourceNotFound error
507
+ - `rejectsWithConditionalCheckFailed()` - Reject with DynamoDB ConditionalCheckFailed error
508
+ - `rejectsWithThrottling()` - Reject with Throttling error
509
+ - `rejectsWithInternalServerError()` - Reject with InternalServerError
217
510
 
218
511
  ## 🤝 Contributing
219
512
 
@@ -250,6 +543,10 @@ bun nx build
250
543
 
251
544
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for the complete guide.
252
545
 
546
+ ## 🙏 Acknowledgements
547
+
548
+ This library was inspired by [aws-sdk-client-mock](https://github.com/m-radzikowski/aws-sdk-client-mock). Adapted the core concepts and API design for Vitest while adding additional features and capabilities.
549
+
253
550
  ## 📝 License
254
551
 
255
552
  MIT
package/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("vitest"),h=require("./matchers-D1XEjFLq.cjs");function m(r,t){return Object.keys(t).every(c=>{const o=t[c],n=r[c];return o&&typeof o=="object"&&!Array.isArray(o)?typeof n!="object"||n===null?!1:m(n,o):n===o})}function y(r,t){if(r===t)return!0;if(typeof r!="object"||r===null||typeof t!="object"||t===null)return r===t;const c=Object.keys(r),o=Object.keys(t);return c.length!==o.length?!1:o.every(n=>{if(!Object.prototype.hasOwnProperty.call(r,n))return!1;const s=r[n],e=t[n];return typeof s=="object"&&s!==null&&typeof e=="object"&&e!==null?y(s,e):s===e})}function k(r){return async function(t){const c=this,o=()=>c,n=r.map.get(t.constructor);if(n){const s=n.findIndex(e=>e.strict?e.matcher&&y(t.input,e.matcher):!e.matcher||m(t.input,e.matcher));if(s!==-1){const e=n[s];return e.once&&n.splice(s,1),e.handler(t.input,o)}}throw new Error(`No mock configured for command: ${t.constructor.name}`)}}function b(r,t,c,o={}){const n=(e,a)=>{const p={matcher:c,handler:e,once:a,strict:!!o.strict},i=r.map.get(t)??[];if(a){const l=i.findIndex(u=>!u.once);l===-1?i.push(p):i.splice(l,0,p),r.map.set(t,i)}else{const l=i.filter(u=>u.once||JSON.stringify(u.matcher)!==JSON.stringify(c));l.push(p),r.map.set(t,l)}},s={resolves(e){return n(()=>Promise.resolve(e),!1),s},rejects(e){return n(()=>{const a=typeof e=="string"?new Error(e):e;return Promise.reject(a)},!1),s},callsFake(e){return n(e,!1),s},resolvesOnce(e){return n(()=>Promise.resolve(e),!0),s},rejectsOnce(e){return n(()=>{const a=typeof e=="string"?new Error(e):e;return Promise.reject(a)},!0),s},callsFakeOnce(e){return n(e,!0),s}};return s}const j=r=>{const t={map:new WeakMap},c=("prototype"in r,r.prototype),o=f.vi.spyOn(c,"send").mockImplementation(k(t));return{client:void 0,on:(s,e,a)=>b(t,s,e,a),reset:()=>{o.mockClear(),t.map=new WeakMap},restore:()=>{o.mockRestore(),t.map=new WeakMap},calls:()=>o.mock.calls}},d=r=>{const t={map:new WeakMap},c=f.vi.spyOn(r,"send").mockImplementation(k(t));return{client:r,on:(n,s,e)=>b(t,n,s,e),reset:()=>{c.mockClear(),t.map=new WeakMap},restore:()=>{c.mockRestore(),t.map=new WeakMap},calls:()=>c.mock.calls}};exports.matchers=h.matchers;exports.mockClient=j;exports.mockClientInstance=d;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("vitest"),P=require("node:fs"),R=require("node:path"),N=require("node:stream"),W=require("./matchers-G36N2DNa.cjs");class d extends Error{constructor(e,s,c,n){super(e),this.name="AwsError",this.code=s,this.statusCode=c,this.retryable=n}}const C=t=>{const e=t?`The specified key does not exist. Key: ${t}`:"The specified key does not exist.";return new d(e,"NoSuchKey",404,!1)},M=t=>{const e=t?`The specified bucket does not exist. Bucket: ${t}`:"The specified bucket does not exist.";return new d(e,"NoSuchBucket",404,!1)},$=t=>{const e=t?`Access Denied for resource: ${t}`:"Access Denied";return new d(e,"AccessDenied",403,!1)},D=t=>{const e=t?`Requested resource not found: ${t}`:"Requested resource not found";return new d(e,"ResourceNotFoundException",400,!1)},I=()=>new d("The conditional request failed","ConditionalCheckFailedException",400,!1),O=()=>new d("Rate exceeded","Throttling",400,!0),T=()=>new d("We encountered an internal error. Please try again.","InternalServerError",500,!0);function k(){return{enabled:!1,log(t,e){this.enabled&&(e===void 0?console.log(`[aws-sdk-vitest-mock](Debug) ${t}`):console.log(`[aws-sdk-vitest-mock](Debug) ${t}`,e))}}}function y(t){t.enabled=!0}function v(t){t.enabled=!1}function F(t){const e=R.resolve(t),s=P.readFileSync(e,"utf8");return t.endsWith(".json")?JSON.parse(s):s}function L(t,e={}){const{pageSize:s=10,tokenKey:c="NextToken",itemsKey:n="Items"}=e;if(t.length===0)return[{[n]:[]}];const o=[];for(let r=0;r<t.length;r+=s){const u=t.slice(r,r+s),i=r+s<t.length,l={[n]:u};if(i){const a=l;a[c]=`token-${r+s}`}o.push(l)}return o}function A(){return typeof process<"u"&&process.versions?.node?"node":typeof process<"u"&&process.versions?.bun?"bun":"browser"}function K(t){const e=typeof t=="string"?Buffer.from(t,"utf8"):Buffer.from(t);return new N.Readable({read(){this.push(e),this.push(void 0)}})}function q(t){let e;return typeof t=="string"?e=new TextEncoder().encode(t):t instanceof Buffer?e=new Uint8Array(t):e=t,new ReadableStream({start(s){s.enqueue(e),s.close()}})}function m(t){const e=A();return e==="node"||e==="bun"?K(t):q(t)}function w(t,e){return Object.keys(e).every(s=>{const c=e[s],n=t[s];return c&&typeof c=="object"&&!Array.isArray(c)?typeof n!="object"||n===null?!1:w(n,c):n===c})}function j(t,e){if(t===e)return!0;if(typeof t!="object"||t===null||typeof e!="object"||e===null)return t===e;const s=Object.keys(t),c=Object.keys(e);return s.length!==c.length?!1:c.every(n=>{if(!Object.prototype.hasOwnProperty.call(t,n))return!1;const o=t,r=e,u=o[n],i=r[n];return typeof u=="object"&&u!==null&&typeof i=="object"&&i!==null?j(u,i):u===i})}function S(t){return async function(e){const s=()=>this;t.debugLogger.log(`Received command: ${e.constructor.name}`,e.input);const c=t.map.get(e.constructor);if(c){t.debugLogger.log(`Found ${c.length} mock(s) for ${e.constructor.name}`);const n=c.findIndex(o=>o.strict?o.matcher&&j(e.input,o.matcher):!o.matcher||w(e.input,o.matcher));if(n===-1)t.debugLogger.log(`No matching mock found for ${e.constructor.name}`,e.input);else{const o=c[n];if(!o)throw new Error(`Mock at index ${n} not found`);return t.debugLogger.log(`Using mock at index ${n} for ${e.constructor.name}`),o.once&&(c.splice(n,1),t.debugLogger.log(`Removed one-time mock for ${e.constructor.name}`)),o.handler(e.input,s())}}else t.debugLogger.log(`No mocks configured for ${e.constructor.name}`);throw new Error(`No mock configured for command: ${e.constructor.name}`)}}function x(t,e,s,c={}){const n=(r,u)=>{const i={matcher:s,handler:r,once:u,strict:!!c.strict},l=t.map.get(e)??[];if(u){const a=l.findIndex(f=>!f.once);a===-1?l.push(i):l.splice(a,0,i),t.map.set(e,l)}else{const a=l.filter(f=>f.once||JSON.stringify(f.matcher)!==JSON.stringify(s));a.push(i),t.map.set(e,a)}},o={resolves(r){return n(()=>Promise.resolve(r),!1),o},rejects(r){return n(()=>{const u=typeof r=="string"?new Error(r):r;return Promise.reject(u)},!1),o},callsFake(r){return n(r,!1),o},resolvesOnce(r){return n(()=>Promise.resolve(r),!0),o},rejectsOnce(r){return n(()=>{const u=typeof r=="string"?new Error(r):r;return Promise.reject(u)},!0),o},callsFakeOnce(r){return n(r,!0),o},resolvesStream(r){return n(()=>Promise.resolve({Body:m(r)}),!1),o},resolvesStreamOnce(r){return n(()=>Promise.resolve({Body:m(r)}),!0),o},resolvesWithDelay(r,u){const i=l=>{setTimeout(()=>l(r),u)};return n(()=>new Promise(i),!1),o},rejectsWithDelay(r,u){const i=typeof r=="string"?new Error(r):r,l=(a,f)=>{setTimeout(()=>f(i),u)};return n(()=>new Promise(l),!1),o},rejectsWithNoSuchKey(r){return n(()=>Promise.reject(C(r)),!1),o},rejectsWithNoSuchBucket(r){return n(()=>Promise.reject(M(r)),!1),o},rejectsWithAccessDenied(r){return n(()=>Promise.reject($(r)),!1),o},rejectsWithResourceNotFound(r){return n(()=>Promise.reject(D(r)),!1),o},rejectsWithConditionalCheckFailed(){return n(()=>Promise.reject(I()),!1),o},rejectsWithThrottling(){return n(()=>Promise.reject(O()),!1),o},rejectsWithInternalServerError(){return n(()=>Promise.reject(T()),!1),o},resolvesPaginated(r,u={}){const i=L(r,u);let l=0;return n(a=>{const f=u.tokenKey||"NextToken",p=a[f];if(p){const g=/token-(\d+)/.exec(p);if(g&&g[1]){const E=g[1];l=Math.floor(Number.parseInt(E,10)/(u.pageSize||10))}}else l=0;const h=i[l]||i[i.length-1]||i[0];if(!h)throw new Error("No paginated responses available");return l=Math.min(l+1,i.length-1),Promise.resolve(h)},!1),o},resolvesFromFile(r){return n(()=>{const u=F(r);return Promise.resolve(u)},!1),o}};return o}const B=t=>{const e={map:new WeakMap,debugLogger:k()},s=t.prototype,c=b.vi.spyOn(s,"send").mockImplementation(S(e));return{client:void 0,on:(o,r,u)=>x(e,o,r,u),reset:()=>{c.mockClear(),e.map=new WeakMap},restore:()=>{c.mockRestore(),e.map=new WeakMap},calls:()=>c.mock.calls,enableDebug:()=>{y(e.debugLogger)},disableDebug:()=>{v(e.debugLogger)}}},V=t=>{const e={map:new WeakMap,debugLogger:k()},s=b.vi.spyOn(t,"send").mockImplementation(S(e));return{client:t,on:(n,o,r)=>x(e,n,o,r),reset:()=>{s.mockClear(),e.map=new WeakMap},restore:()=>{s.mockRestore(),e.map=new WeakMap},calls:()=>s.mock.calls,enableDebug:()=>{y(e.debugLogger)},disableDebug:()=>{v(e.debugLogger)}}};exports.matchers=W.matchers;exports.mockClient=B;exports.mockClientInstance=V;
package/index.js CHANGED
@@ -1,113 +1,344 @@
1
- import { vi as f } from "vitest";
2
- import { m as w } from "./matchers-DclpcT4f.js";
3
- function m(r, t) {
4
- return Object.keys(t).every((c) => {
5
- const o = t[c], n = r[c];
6
- return o && typeof o == "object" && !Array.isArray(o) ? typeof n != "object" || n === null ? !1 : m(n, o) : n === o;
1
+ import { vi as b } from "vitest";
2
+ import { readFileSync as P } from "node:fs";
3
+ import R from "node:path";
4
+ import { Readable as N } from "node:stream";
5
+ import { m as X } from "./matchers-DdPGAOsD.js";
6
+ class d extends Error {
7
+ constructor(e, s, c, n) {
8
+ super(e), this.name = "AwsError", this.code = s, this.statusCode = c, this.retryable = n;
9
+ }
10
+ }
11
+ const W = (t) => {
12
+ const e = t ? `The specified key does not exist. Key: ${t}` : "The specified key does not exist.";
13
+ return new d(e, "NoSuchKey", 404, !1);
14
+ }, $ = (t) => {
15
+ const e = t ? `The specified bucket does not exist. Bucket: ${t}` : "The specified bucket does not exist.";
16
+ return new d(e, "NoSuchBucket", 404, !1);
17
+ }, D = (t) => {
18
+ const e = t ? `Access Denied for resource: ${t}` : "Access Denied";
19
+ return new d(e, "AccessDenied", 403, !1);
20
+ }, M = (t) => {
21
+ const e = t ? `Requested resource not found: ${t}` : "Requested resource not found";
22
+ return new d(e, "ResourceNotFoundException", 400, !1);
23
+ }, C = () => new d(
24
+ "The conditional request failed",
25
+ "ConditionalCheckFailedException",
26
+ 400,
27
+ !1
28
+ ), I = () => new d("Rate exceeded", "Throttling", 400, !0), O = () => new d(
29
+ "We encountered an internal error. Please try again.",
30
+ "InternalServerError",
31
+ 500,
32
+ !0
33
+ );
34
+ function k() {
35
+ return {
36
+ enabled: !1,
37
+ log(t, e) {
38
+ this.enabled && (e === void 0 ? console.log(`[aws-sdk-vitest-mock](Debug) ${t}`) : console.log(`[aws-sdk-vitest-mock](Debug) ${t}`, e));
39
+ }
40
+ };
41
+ }
42
+ function y(t) {
43
+ t.enabled = !0;
44
+ }
45
+ function v(t) {
46
+ t.enabled = !1;
47
+ }
48
+ function T(t) {
49
+ const e = R.resolve(t), s = P(e, "utf8");
50
+ return t.endsWith(".json") ? JSON.parse(s) : s;
51
+ }
52
+ function F(t, e = {}) {
53
+ const { pageSize: s = 10, tokenKey: c = "NextToken", itemsKey: n = "Items" } = e;
54
+ if (t.length === 0)
55
+ return [{ [n]: [] }];
56
+ const o = [];
57
+ for (let r = 0; r < t.length; r += s) {
58
+ const u = t.slice(r, r + s), i = r + s < t.length, l = { [n]: u };
59
+ if (i) {
60
+ const a = l;
61
+ a[c] = `token-${r + s}`;
62
+ }
63
+ o.push(l);
64
+ }
65
+ return o;
66
+ }
67
+ function L() {
68
+ return typeof process < "u" && process.versions?.node ? "node" : typeof process < "u" && process.versions?.bun ? "bun" : "browser";
69
+ }
70
+ function A(t) {
71
+ const e = typeof t == "string" ? Buffer.from(t, "utf8") : Buffer.from(t);
72
+ return new N({
73
+ read() {
74
+ this.push(e), this.push(void 0);
75
+ }
76
+ });
77
+ }
78
+ function K(t) {
79
+ let e;
80
+ return typeof t == "string" ? e = new TextEncoder().encode(t) : t instanceof Buffer ? e = new Uint8Array(t) : e = t, new ReadableStream({
81
+ start(s) {
82
+ s.enqueue(e), s.close();
83
+ }
84
+ });
85
+ }
86
+ function h(t) {
87
+ const e = L();
88
+ return e === "node" || e === "bun" ? A(t) : K(t);
89
+ }
90
+ function w(t, e) {
91
+ return Object.keys(e).every((s) => {
92
+ const c = e[s], n = t[s];
93
+ return c && typeof c == "object" && !Array.isArray(c) ? typeof n != "object" || n === null ? !1 : w(
94
+ n,
95
+ c
96
+ ) : n === c;
7
97
  });
8
98
  }
9
- function y(r, t) {
10
- if (r === t) return !0;
11
- if (typeof r != "object" || r === null || typeof t != "object" || t === null)
12
- return r === t;
13
- const c = Object.keys(r), o = Object.keys(t);
14
- return c.length !== o.length ? !1 : o.every((n) => {
15
- if (!Object.prototype.hasOwnProperty.call(r, n)) return !1;
16
- const s = r[n], e = t[n];
17
- return typeof s == "object" && s !== null && typeof e == "object" && e !== null ? y(s, e) : s === e;
99
+ function j(t, e) {
100
+ if (t === e) return !0;
101
+ if (typeof t != "object" || t === null || typeof e != "object" || e === null)
102
+ return t === e;
103
+ const s = Object.keys(t), c = Object.keys(e);
104
+ return s.length !== c.length ? !1 : c.every((n) => {
105
+ if (!Object.prototype.hasOwnProperty.call(t, n)) return !1;
106
+ const o = t, r = e, u = o[n], i = r[n];
107
+ return typeof u == "object" && u !== null && typeof i == "object" && i !== null ? j(u, i) : u === i;
18
108
  });
19
109
  }
20
- function k(r) {
21
- return async function(t) {
22
- const c = this, o = () => c, n = r.map.get(t.constructor);
23
- if (n) {
24
- const s = n.findIndex((e) => e.strict ? e.matcher && y(t.input, e.matcher) : !e.matcher || m(t.input, e.matcher));
25
- if (s !== -1) {
26
- const e = n[s];
27
- return e.once && n.splice(s, 1), e.handler(t.input, o);
110
+ function x(t) {
111
+ return async function(e) {
112
+ const s = () => this;
113
+ t.debugLogger.log(
114
+ `Received command: ${e.constructor.name}`,
115
+ e.input
116
+ );
117
+ const c = t.map.get(
118
+ e.constructor
119
+ );
120
+ if (c) {
121
+ t.debugLogger.log(
122
+ `Found ${c.length} mock(s) for ${e.constructor.name}`
123
+ );
124
+ const n = c.findIndex((o) => o.strict ? o.matcher && j(e.input, o.matcher) : !o.matcher || w(e.input, o.matcher));
125
+ if (n === -1)
126
+ t.debugLogger.log(
127
+ `No matching mock found for ${e.constructor.name}`,
128
+ e.input
129
+ );
130
+ else {
131
+ const o = c[n];
132
+ if (!o)
133
+ throw new Error(`Mock at index ${n} not found`);
134
+ return t.debugLogger.log(
135
+ `Using mock at index ${n} for ${e.constructor.name}`
136
+ ), o.once && (c.splice(n, 1), t.debugLogger.log(
137
+ `Removed one-time mock for ${e.constructor.name}`
138
+ )), o.handler(
139
+ e.input,
140
+ s()
141
+ );
28
142
  }
29
- }
30
- throw new Error(`No mock configured for command: ${t.constructor.name}`);
143
+ } else
144
+ t.debugLogger.log(
145
+ `No mocks configured for ${e.constructor.name}`
146
+ );
147
+ throw new Error(
148
+ `No mock configured for command: ${e.constructor.name}`
149
+ );
31
150
  };
32
151
  }
33
- function b(r, t, c, o = {}) {
34
- const n = (e, a) => {
35
- const p = {
36
- matcher: c,
37
- handler: e,
38
- once: a,
39
- strict: !!o.strict
40
- }, i = r.map.get(t) ?? [];
41
- if (a) {
42
- const l = i.findIndex((u) => !u.once);
43
- l === -1 ? i.push(p) : i.splice(l, 0, p), r.map.set(t, i);
152
+ function S(t, e, s, c = {}) {
153
+ const n = (r, u) => {
154
+ const i = {
155
+ matcher: s,
156
+ handler: r,
157
+ once: u,
158
+ strict: !!c.strict
159
+ }, l = t.map.get(e) ?? [];
160
+ if (u) {
161
+ const a = l.findIndex((f) => !f.once);
162
+ a === -1 ? l.push(i) : l.splice(a, 0, i), t.map.set(
163
+ e,
164
+ l
165
+ );
44
166
  } else {
45
- const l = i.filter(
46
- (u) => u.once || JSON.stringify(u.matcher) !== JSON.stringify(c)
167
+ const a = l.filter(
168
+ (f) => f.once || JSON.stringify(f.matcher) !== JSON.stringify(s)
169
+ );
170
+ a.push(i), t.map.set(
171
+ e,
172
+ a
47
173
  );
48
- l.push(p), r.map.set(t, l);
49
174
  }
50
- }, s = {
51
- resolves(e) {
52
- return n(() => Promise.resolve(e), !1), s;
175
+ }, o = {
176
+ resolves(r) {
177
+ return n(() => Promise.resolve(r), !1), o;
53
178
  },
54
- rejects(e) {
179
+ rejects(r) {
55
180
  return n(() => {
56
- const a = typeof e == "string" ? new Error(e) : e;
57
- return Promise.reject(a);
58
- }, !1), s;
181
+ const u = typeof r == "string" ? new Error(r) : r;
182
+ return Promise.reject(u);
183
+ }, !1), o;
59
184
  },
60
- callsFake(e) {
61
- return n(e, !1), s;
185
+ callsFake(r) {
186
+ return n(r, !1), o;
62
187
  },
63
- resolvesOnce(e) {
64
- return n(() => Promise.resolve(e), !0), s;
188
+ resolvesOnce(r) {
189
+ return n(() => Promise.resolve(r), !0), o;
65
190
  },
66
- rejectsOnce(e) {
191
+ rejectsOnce(r) {
67
192
  return n(() => {
68
- const a = typeof e == "string" ? new Error(e) : e;
69
- return Promise.reject(a);
70
- }, !0), s;
193
+ const u = typeof r == "string" ? new Error(r) : r;
194
+ return Promise.reject(u);
195
+ }, !0), o;
196
+ },
197
+ callsFakeOnce(r) {
198
+ return n(r, !0), o;
199
+ },
200
+ resolvesStream(r) {
201
+ return n(
202
+ () => Promise.resolve({ Body: h(r) }),
203
+ !1
204
+ ), o;
205
+ },
206
+ resolvesStreamOnce(r) {
207
+ return n(
208
+ () => Promise.resolve({ Body: h(r) }),
209
+ !0
210
+ ), o;
211
+ },
212
+ resolvesWithDelay(r, u) {
213
+ const i = (l) => {
214
+ setTimeout(() => l(r), u);
215
+ };
216
+ return n(() => new Promise(i), !1), o;
217
+ },
218
+ rejectsWithDelay(r, u) {
219
+ const i = typeof r == "string" ? new Error(r) : r, l = (a, f) => {
220
+ setTimeout(() => f(i), u);
221
+ };
222
+ return n(() => new Promise(l), !1), o;
223
+ },
224
+ rejectsWithNoSuchKey(r) {
225
+ return n(() => Promise.reject(W(r)), !1), o;
226
+ },
227
+ rejectsWithNoSuchBucket(r) {
228
+ return n(() => Promise.reject($(r)), !1), o;
229
+ },
230
+ rejectsWithAccessDenied(r) {
231
+ return n(() => Promise.reject(D(r)), !1), o;
71
232
  },
72
- callsFakeOnce(e) {
73
- return n(e, !0), s;
233
+ rejectsWithResourceNotFound(r) {
234
+ return n(
235
+ () => Promise.reject(M(r)),
236
+ !1
237
+ ), o;
238
+ },
239
+ rejectsWithConditionalCheckFailed() {
240
+ return n(
241
+ () => Promise.reject(C()),
242
+ !1
243
+ ), o;
244
+ },
245
+ rejectsWithThrottling() {
246
+ return n(() => Promise.reject(I()), !1), o;
247
+ },
248
+ rejectsWithInternalServerError() {
249
+ return n(() => Promise.reject(O()), !1), o;
250
+ },
251
+ resolvesPaginated(r, u = {}) {
252
+ const i = F(r, u);
253
+ let l = 0;
254
+ return n((a) => {
255
+ const f = u.tokenKey || "NextToken", p = a[f];
256
+ if (p) {
257
+ const g = /token-(\d+)/.exec(p);
258
+ if (g && g[1]) {
259
+ const E = g[1];
260
+ l = Math.floor(
261
+ Number.parseInt(E, 10) / (u.pageSize || 10)
262
+ );
263
+ }
264
+ } else
265
+ l = 0;
266
+ const m = (
267
+ // eslint-disable-next-line security/detect-object-injection
268
+ i[l] || // eslint-disable-next-line unicorn/prefer-at -- TypeScript target doesn't support Array.at() method
269
+ i[i.length - 1] || i[0]
270
+ );
271
+ if (!m)
272
+ throw new Error("No paginated responses available");
273
+ return l = Math.min(l + 1, i.length - 1), Promise.resolve(m);
274
+ }, !1), o;
275
+ },
276
+ resolvesFromFile(r) {
277
+ return n(() => {
278
+ const u = T(r);
279
+ return Promise.resolve(u);
280
+ }, !1), o;
74
281
  }
75
282
  };
76
- return s;
283
+ return o;
77
284
  }
78
- const j = (r) => {
79
- const t = {
80
- map: /* @__PURE__ */ new WeakMap()
81
- }, c = ("prototype" in r, r.prototype), o = f.spyOn(c, "send").mockImplementation(k(t));
285
+ const _ = (t) => {
286
+ const e = {
287
+ map: /* @__PURE__ */ new WeakMap(),
288
+ debugLogger: k()
289
+ }, s = t.prototype, c = b.spyOn(s, "send").mockImplementation(x(e));
82
290
  return {
83
291
  client: void 0,
84
- on: (s, e, a) => b(t, s, e, a),
292
+ on: (o, r, u) => S(
293
+ e,
294
+ o,
295
+ r,
296
+ u
297
+ ),
85
298
  reset: () => {
86
- o.mockClear(), t.map = /* @__PURE__ */ new WeakMap();
299
+ c.mockClear(), e.map = /* @__PURE__ */ new WeakMap();
87
300
  },
88
301
  restore: () => {
89
- o.mockRestore(), t.map = /* @__PURE__ */ new WeakMap();
302
+ c.mockRestore(), e.map = /* @__PURE__ */ new WeakMap();
303
+ },
304
+ calls: () => c.mock.calls,
305
+ enableDebug: () => {
306
+ y(e.debugLogger);
90
307
  },
91
- calls: () => o.mock.calls
308
+ disableDebug: () => {
309
+ v(e.debugLogger);
310
+ }
92
311
  };
93
- }, d = (r) => {
94
- const t = {
95
- map: /* @__PURE__ */ new WeakMap()
96
- }, c = f.spyOn(r, "send").mockImplementation(k(t));
312
+ }, G = (t) => {
313
+ const e = {
314
+ map: /* @__PURE__ */ new WeakMap(),
315
+ debugLogger: k()
316
+ }, s = b.spyOn(t, "send").mockImplementation(x(e));
97
317
  return {
98
- client: r,
99
- on: (n, s, e) => b(t, n, s, e),
318
+ client: t,
319
+ on: (n, o, r) => S(
320
+ e,
321
+ n,
322
+ o,
323
+ r
324
+ ),
100
325
  reset: () => {
101
- c.mockClear(), t.map = /* @__PURE__ */ new WeakMap();
326
+ s.mockClear(), e.map = /* @__PURE__ */ new WeakMap();
102
327
  },
103
328
  restore: () => {
104
- c.mockRestore(), t.map = /* @__PURE__ */ new WeakMap();
329
+ s.mockRestore(), e.map = /* @__PURE__ */ new WeakMap();
105
330
  },
106
- calls: () => c.mock.calls
331
+ calls: () => s.mock.calls,
332
+ enableDebug: () => {
333
+ y(e.debugLogger);
334
+ },
335
+ disableDebug: () => {
336
+ v(e.debugLogger);
337
+ }
107
338
  };
108
339
  };
109
340
  export {
110
- w as matchers,
111
- j as mockClient,
112
- d as mockClientInstance
341
+ X as matchers,
342
+ _ as mockClient,
343
+ G as mockClientInstance
113
344
  };
package/lib/matchers.d.ts CHANGED
@@ -1,29 +1,31 @@
1
- import { AwsClientStub } from './mock-client.js';
2
- type CommandConstructor = new (...args: unknown[]) => unknown;
1
+ import { MetadataBearer } from '@smithy/types';
2
+ import { AwsClientStub, CommandConstructor } from './mock-client.js';
3
3
  interface MatcherResult {
4
4
  pass: boolean;
5
5
  message: () => string;
6
6
  }
7
7
  export declare const matchers: {
8
- toHaveReceivedCommand(received: AwsClientStub, command: CommandConstructor): MatcherResult;
9
- toHaveReceivedCommandTimes(received: AwsClientStub, command: CommandConstructor, times: number): MatcherResult;
10
- toHaveReceivedCommandWith(this: {
8
+ toHaveReceivedCommand<TInput extends object, TOutput extends MetadataBearer>(received: AwsClientStub, command: CommandConstructor<TInput, TOutput>): MatcherResult;
9
+ toHaveReceivedCommandTimes<TInput extends object, TOutput extends MetadataBearer>(received: AwsClientStub, command: CommandConstructor<TInput, TOutput>, times: number): MatcherResult;
10
+ toHaveReceivedCommandWith<TInput extends object, TOutput extends MetadataBearer>(this: {
11
11
  equals: (a: unknown, b: unknown) => boolean;
12
- }, received: AwsClientStub, command: CommandConstructor, input: Record<string, unknown>): MatcherResult;
13
- toHaveReceivedNthCommandWith(this: {
12
+ }, received: AwsClientStub, command: CommandConstructor<TInput, TOutput>, input: TInput): MatcherResult;
13
+ toHaveReceivedNthCommandWith<TInput extends object, TOutput extends MetadataBearer>(this: {
14
14
  equals: (a: unknown, b: unknown) => boolean;
15
- }, received: AwsClientStub, n: number, command: CommandConstructor, input: Record<string, unknown>): MatcherResult;
15
+ }, received: AwsClientStub, n: number, command: CommandConstructor<TInput, TOutput>, input: TInput): MatcherResult;
16
+ toHaveReceivedNoOtherCommands<TInput extends object, TOutput extends MetadataBearer>(received: AwsClientStub, expectedCommands?: CommandConstructor<TInput, TOutput>[]): MatcherResult;
16
17
  };
17
18
  export interface AwsSdkMatchers<R = unknown> {
18
- toHaveReceivedCommand(command: CommandConstructor): R;
19
- toHaveReceivedCommandTimes(command: CommandConstructor, times: number): R;
20
- toHaveReceivedCommandWith(command: CommandConstructor, input: Record<string, unknown>): R;
21
- toHaveReceivedNthCommandWith(n: number, command: CommandConstructor, input: Record<string, unknown>): R;
19
+ toHaveReceivedCommand<TInput extends object, TOutput extends MetadataBearer>(command: CommandConstructor<TInput, TOutput>): R;
20
+ toHaveReceivedCommandTimes<TInput extends object, TOutput extends MetadataBearer>(command: CommandConstructor<TInput, TOutput>, times: number): R;
21
+ toHaveReceivedCommandWith<TInput extends object, TOutput extends MetadataBearer>(command: CommandConstructor<TInput, TOutput>, input: TInput): R;
22
+ toHaveReceivedNthCommandWith<TInput extends object, TOutput extends MetadataBearer>(n: number, command: CommandConstructor<TInput, TOutput>, input: TInput): R;
23
+ toHaveReceivedNoOtherCommands<TInput extends object, TOutput extends MetadataBearer>(expectedCommands?: CommandConstructor<TInput, TOutput>[]): R;
22
24
  }
23
- declare module 'vitest' {
25
+ export type { MatcherResult };
26
+ declare module "vitest" {
24
27
  interface Assertion extends AwsSdkMatchers {
25
28
  }
26
29
  interface AsymmetricMatchersContaining extends AwsSdkMatchers {
27
30
  }
28
31
  }
29
- export {};
@@ -1,33 +1,36 @@
1
- import { SmithyResolvedConfiguration } from '@smithy/smithy-client';
2
- import { MiddlewareStack, Handler } from '@smithy/types';
1
+ import { Command as SmithyCommand, SmithyResolvedConfiguration } from '@smithy/smithy-client';
2
+ import { HttpHandlerOptions, MetadataBearer } from '@smithy/types';
3
3
  import { Mock } from 'vitest';
4
- export interface MetadataBearer {
5
- $metadata?: unknown;
6
- }
7
- export interface StructuralCommand<TInput extends object, TOutput extends MetadataBearer> {
4
+ import { PaginatorOptions } from './utils/paginator-helpers.js';
5
+ import { StreamInput } from './utils/stream-helpers.js';
6
+ export type StructuralCommand<TInput extends object, TOutput extends MetadataBearer> = SmithyCommand<TInput, TOutput, any, any, any> | {
8
7
  readonly input: TInput;
9
- middlewareStack: MiddlewareStack<TInput, TOutput>;
10
- resolveMiddleware(stack: MiddlewareStack<TInput, TOutput>, configuration: unknown, options: unknown): Handler<TInput, TOutput>;
11
- }
8
+ readonly __awsSdkVitestMockOutput?: TOutput;
9
+ };
12
10
  export type CommandConstructor<TInput extends object, TOutput extends MetadataBearer> = new (input: TInput) => StructuralCommand<TInput, TOutput>;
11
+ type AwsCommandConstructor = CommandConstructor<any, MetadataBearer>;
12
+ export type CommandInputType<TCtor extends AwsCommandConstructor> = ConstructorParameters<TCtor>[0];
13
+ export type CommandOutputType<TCtor extends AwsCommandConstructor> = InstanceType<TCtor> extends StructuralCommand<any, infer TOutput> ? TOutput : MetadataBearer;
13
14
  export type AnyClient = {
14
15
  send(command: AnyCommand): Promise<MetadataBearer>;
15
- config: SmithyResolvedConfiguration<unknown>;
16
+ config: SmithyResolvedConfiguration<HttpHandlerOptions> | Record<string, unknown>;
16
17
  };
17
18
  export type AnyCommand = StructuralCommand<object, MetadataBearer>;
18
- export type ClientConstructor<TClient extends AnyClient> = (abstract new (config: unknown) => TClient) | {
19
+ export type ClientConstructor<TClient extends AnyClient> = (abstract new (...args: unknown[]) => TClient) | {
19
20
  prototype: TClient;
20
21
  };
21
- type CommandHandler<TInput extends object = object, TOutput extends MetadataBearer = MetadataBearer, TClient extends AnyClient = AnyClient> = (input: TInput, getClient: () => TClient | undefined) => Promise<TOutput>;
22
+ type CommandHandler<TInput extends object = object, TOutput extends MetadataBearer = MetadataBearer, TClient extends AnyClient = AnyClient> = (input: TInput, clientInstance: TClient | undefined) => Promise<Partial<TOutput>>;
22
23
  interface MockOptions {
23
24
  strict?: boolean;
24
25
  }
25
26
  export interface AwsClientStub<TClient extends AnyClient = AnyClient> {
26
27
  readonly client: TClient | undefined;
27
- on: <TInput extends object, TOutput extends MetadataBearer>(command: CommandConstructor<TInput, TOutput>, request?: Partial<TInput>, options?: MockOptions) => AwsCommandStub<TInput, TOutput, TClient>;
28
+ on: <TCtor extends AwsCommandConstructor>(command: TCtor, request?: Partial<CommandInputType<TCtor>>, options?: MockOptions) => AwsCommandStub<CommandInputType<TCtor>, CommandOutputType<TCtor>, TClient>;
28
29
  reset: () => void;
29
30
  restore: () => void;
30
- calls: () => ReturnType<Mock['mock']['calls']['slice']>;
31
+ calls: () => ReturnType<Mock["mock"]["calls"]["slice"]>;
32
+ enableDebug: () => void;
33
+ disableDebug: () => void;
31
34
  }
32
35
  export interface AwsCommandStub<TInput extends object, TOutput extends MetadataBearer, TClient extends AnyClient = AnyClient> {
33
36
  /** Set a permanent mock response (used after all once handlers are consumed) */
@@ -42,6 +45,32 @@ export interface AwsCommandStub<TInput extends object, TOutput extends MetadataB
42
45
  rejectsOnce: (error: Error | string) => AwsCommandStub<TInput, TOutput, TClient>;
43
46
  /** Add a one-time custom handler (consumed in order) */
44
47
  callsFakeOnce: (fn: CommandHandler<TInput, TOutput, TClient>) => AwsCommandStub<TInput, TOutput, TClient>;
48
+ /** Set a permanent stream response for S3-like operations */
49
+ resolvesStream: (data: StreamInput) => AwsCommandStub<TInput, TOutput, TClient>;
50
+ /** Set a one-time stream response for S3-like operations */
51
+ resolvesStreamOnce: (data: StreamInput) => AwsCommandStub<TInput, TOutput, TClient>;
52
+ /** Set a permanent mock response with delay */
53
+ resolvesWithDelay: (output: Partial<TOutput>, delayMs: number) => AwsCommandStub<TInput, TOutput, TClient>;
54
+ /** Set a permanent mock rejection with delay */
55
+ rejectsWithDelay: (error: Error | string, delayMs: number) => AwsCommandStub<TInput, TOutput, TClient>;
56
+ /** Reject with S3 NoSuchKey error */
57
+ rejectsWithNoSuchKey: (key?: string) => AwsCommandStub<TInput, TOutput, TClient>;
58
+ /** Reject with S3 NoSuchBucket error */
59
+ rejectsWithNoSuchBucket: (bucket?: string) => AwsCommandStub<TInput, TOutput, TClient>;
60
+ /** Reject with AccessDenied error */
61
+ rejectsWithAccessDenied: (resource?: string) => AwsCommandStub<TInput, TOutput, TClient>;
62
+ /** Reject with DynamoDB ResourceNotFound error */
63
+ rejectsWithResourceNotFound: (resource?: string) => AwsCommandStub<TInput, TOutput, TClient>;
64
+ /** Reject with DynamoDB ConditionalCheckFailed error */
65
+ rejectsWithConditionalCheckFailed: () => AwsCommandStub<TInput, TOutput, TClient>;
66
+ /** Reject with Throttling error */
67
+ rejectsWithThrottling: () => AwsCommandStub<TInput, TOutput, TClient>;
68
+ /** Reject with InternalServerError */
69
+ rejectsWithInternalServerError: () => AwsCommandStub<TInput, TOutput, TClient>;
70
+ /** Set paginated responses for AWS pagination */
71
+ resolvesPaginated: <T = unknown>(items: T[], options?: PaginatorOptions) => AwsCommandStub<TInput, TOutput, TClient>;
72
+ /** Load response from file (JSON files are parsed, others returned as strings) */
73
+ resolvesFromFile: (filePath: string) => AwsCommandStub<TInput, TOutput, TClient>;
45
74
  }
46
75
  export declare const mockClient: <TClient extends AnyClient>(clientConstructor: ClientConstructor<TClient>) => AwsClientStub<TClient>;
47
76
  export declare const mockClientInstance: <TClient extends AnyClient>(clientInstance: TClient) => AwsClientStub<AnyClient>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Common AWS SDK error types and factory functions
3
+ */
4
+ export declare class AwsError extends Error {
5
+ readonly name: string;
6
+ readonly code: string;
7
+ readonly statusCode?: number;
8
+ readonly retryable?: boolean;
9
+ constructor(message: string, code: string, statusCode?: number, retryable?: boolean);
10
+ }
11
+ export declare const createNoSuchKeyError: (key?: string) => AwsError;
12
+ export declare const createNoSuchBucketError: (bucket?: string) => AwsError;
13
+ export declare const createAccessDeniedError: (resource?: string) => AwsError;
14
+ export declare const createResourceNotFoundError: (resource?: string) => AwsError;
15
+ export declare const createConditionalCheckFailedError: () => AwsError;
16
+ export declare const createThrottlingError: () => AwsError;
17
+ export declare const createInternalServerError: () => AwsError;
@@ -0,0 +1,10 @@
1
+ export declare const colors: {
2
+ readonly red: (text: string) => string;
3
+ readonly green: (text: string) => string;
4
+ readonly yellow: (text: string) => string;
5
+ readonly blue: (text: string) => string;
6
+ readonly magenta: (text: string) => string;
7
+ readonly cyan: (text: string) => string;
8
+ readonly gray: (text: string) => string;
9
+ readonly bold: (text: string) => string;
10
+ };
@@ -0,0 +1,7 @@
1
+ export interface DebugLogger {
2
+ enabled: boolean;
3
+ log: (message: string, data?: unknown) => void;
4
+ }
5
+ export declare function createDebugLogger(): DebugLogger;
6
+ export declare function enableDebug(logger: DebugLogger): void;
7
+ export declare function disableDebug(logger: DebugLogger): void;
@@ -0,0 +1 @@
1
+ export declare function loadFixture(filePath: string): unknown;
@@ -0,0 +1,13 @@
1
+ export interface PaginatorOptions {
2
+ pageSize?: number;
3
+ tokenKey?: string;
4
+ itemsKey?: string;
5
+ }
6
+ export interface PaginatedResponse<T = unknown> {
7
+ [key: string]: unknown;
8
+ NextToken?: string;
9
+ ContinuationToken?: string;
10
+ Items?: T[];
11
+ Contents?: T[];
12
+ }
13
+ export declare function createPaginatedResponses<T>(items: T[], options?: PaginatorOptions): PaginatedResponse<T>[];
@@ -0,0 +1,6 @@
1
+ import { Readable } from 'node:stream';
2
+ export type StreamInput = string | Buffer | Uint8Array;
3
+ /**
4
+ * Creates an appropriate stream for the current environment
5
+ */
6
+ export declare function createStream(data: StreamInput): Readable | ReadableStream<Uint8Array>;
@@ -0,0 +1,124 @@
1
+ const e = {
2
+ red: (t) => `\x1B[31m${t}\x1B[39m`,
3
+ green: (t) => `\x1B[32m${t}\x1B[39m`,
4
+ yellow: (t) => `\x1B[33m${t}\x1B[39m`,
5
+ blue: (t) => `\x1B[34m${t}\x1B[39m`,
6
+ magenta: (t) => `\x1B[35m${t}\x1B[39m`,
7
+ cyan: (t) => `\x1B[36m${t}\x1B[39m`,
8
+ gray: (t) => `\x1B[90m${t}\x1B[39m`,
9
+ bold: (t) => `\x1B[1m${t}\x1B[22m`
10
+ }, $ = (t) => {
11
+ const c = t.calls();
12
+ return Array.isArray(c) ? c.filter(
13
+ (a) => Array.isArray(a) && a.length > 0
14
+ ) : [];
15
+ }, f = {
16
+ toHaveReceivedCommand(t, c) {
17
+ const a = $(t), r = a.some((o) => o[0] instanceof c), m = c.name;
18
+ return {
19
+ pass: r,
20
+ message: () => {
21
+ if (r)
22
+ return `Expected AWS SDK mock not to have received command ${e.red(m)}`;
23
+ const o = a.map((n) => n[0].constructor?.name ?? "Unknown");
24
+ return o.length === 0 ? `Expected AWS SDK mock to have received command ${e.red(m)}, but ${e.gray("no commands were received")}` : `Expected AWS SDK mock to have received command ${e.red(m)}, but received: ${e.yellow(o.join(", "))}`;
25
+ }
26
+ };
27
+ },
28
+ toHaveReceivedCommandTimes(t, c, a) {
29
+ const r = $(t).filter(
30
+ (n) => n[0] instanceof c
31
+ ), m = r.length === a, o = c.name;
32
+ return {
33
+ pass: m,
34
+ message: () => m ? `Expected AWS SDK mock not to have received command ${o} ${a} times` : `Expected AWS SDK mock to have received command ${o} ${a} times, but received ${r.length} times`
35
+ };
36
+ },
37
+ toHaveReceivedCommandWith(t, c, a) {
38
+ const r = $(t).filter(
39
+ (n) => n[0] instanceof c
40
+ ), m = r.some((n) => this.equals(n[0].input, a)), o = c.name;
41
+ return {
42
+ pass: m,
43
+ message: () => {
44
+ if (m) {
45
+ const s = e.red(o), u = e.cyan(
46
+ JSON.stringify(a, void 0, 2)
47
+ );
48
+ return `Expected AWS SDK mock not to have received command ${s} with input:
49
+ ${u}`;
50
+ }
51
+ if (r.length === 0) {
52
+ const s = e.red(o), u = e.cyan(
53
+ JSON.stringify(a, void 0, 2)
54
+ ), i = e.gray(`${o} was never called`);
55
+ return `Expected AWS SDK mock to have received command ${s} with input:
56
+ ${u}
57
+
58
+ But ${i}`;
59
+ }
60
+ const n = r.map((s) => s[0].input), d = e.red(o), g = e.cyan(JSON.stringify(a, void 0, 2)), l = e.gray("But received"), h = e.yellow(o), p = e.gray("with"), S = e.yellow(
61
+ n.map((s) => JSON.stringify(s, void 0, 2)).join(`
62
+
63
+ `)
64
+ );
65
+ return `Expected AWS SDK mock to have received command ${d} with input:
66
+ ${g}
67
+
68
+ ${l} ${h} ${p}:
69
+ ${S}`;
70
+ }
71
+ };
72
+ },
73
+ toHaveReceivedNthCommandWith(t, c, a, r) {
74
+ const m = $(t), o = m[c - 1], n = o?.[0], d = n?.input, g = !!o && n instanceof a && this.equals(d, r), l = a.name;
75
+ return {
76
+ pass: g,
77
+ message: () => {
78
+ if (g) {
79
+ const i = e.magenta(c.toString()), v = e.red(l), y = e.cyan(JSON.stringify(r, void 0, 2));
80
+ return `Expected AWS SDK mock not to have received nth (${i}) command ${v} with input:
81
+ ${y}`;
82
+ }
83
+ if (!o) {
84
+ const i = e.magenta(c.toString()), v = e.gray(
85
+ `only received ${m.length} call(s)`
86
+ );
87
+ return `Expected AWS SDK mock to have received at least ${i} call(s), but ${v}`;
88
+ }
89
+ if (!(n instanceof a)) {
90
+ const i = n?.constructor?.name ?? typeof n, v = e.magenta(c.toString()), y = e.red(l), x = e.yellow(i);
91
+ return `Expected AWS SDK mock nth (${v}) call to be ${y}, but received ${x}`;
92
+ }
93
+ const h = e.magenta(c.toString()), p = e.red(l), S = e.cyan(JSON.stringify(r, void 0, 2)), s = e.gray("But received"), u = e.yellow(
94
+ JSON.stringify(d, void 0, 2)
95
+ );
96
+ return `Expected AWS SDK mock nth (${h}) command ${p} with input:
97
+ ${S}
98
+
99
+ ${s}:
100
+ ${u}`;
101
+ }
102
+ };
103
+ },
104
+ toHaveReceivedNoOtherCommands(t, c = []) {
105
+ const r = $(t).filter((o) => {
106
+ const n = o[0];
107
+ return !c.some(
108
+ (d) => n instanceof d
109
+ );
110
+ }), m = r.length === 0;
111
+ return {
112
+ pass: m,
113
+ message: () => {
114
+ if (m)
115
+ return `Expected AWS SDK mock to have received other commands besides ${e.green(c.map((n) => n.name).join(", "))}`;
116
+ const o = r.map((n) => n[0].constructor?.name ?? "Unknown");
117
+ return `Expected AWS SDK mock to have received ${e.gray("no other commands")}, but received: ${e.red(o.join(", "))}`;
118
+ }
119
+ };
120
+ }
121
+ };
122
+ export {
123
+ f as m
124
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";const e={red:t=>`\x1B[31m${t}\x1B[39m`,green:t=>`\x1B[32m${t}\x1B[39m`,yellow:t=>`\x1B[33m${t}\x1B[39m`,blue:t=>`\x1B[34m${t}\x1B[39m`,magenta:t=>`\x1B[35m${t}\x1B[39m`,cyan:t=>`\x1B[36m${t}\x1B[39m`,gray:t=>`\x1B[90m${t}\x1B[39m`,bold:t=>`\x1B[1m${t}\x1B[22m`},$=t=>{const c=t.calls();return Array.isArray(c)?c.filter(a=>Array.isArray(a)&&a.length>0):[]},f={toHaveReceivedCommand(t,c){const a=$(t),r=a.some(o=>o[0]instanceof c),m=c.name;return{pass:r,message:()=>{if(r)return`Expected AWS SDK mock not to have received command ${e.red(m)}`;const o=a.map(n=>n[0].constructor?.name??"Unknown");return o.length===0?`Expected AWS SDK mock to have received command ${e.red(m)}, but ${e.gray("no commands were received")}`:`Expected AWS SDK mock to have received command ${e.red(m)}, but received: ${e.yellow(o.join(", "))}`}}},toHaveReceivedCommandTimes(t,c,a){const r=$(t).filter(n=>n[0]instanceof c),m=r.length===a,o=c.name;return{pass:m,message:()=>m?`Expected AWS SDK mock not to have received command ${o} ${a} times`:`Expected AWS SDK mock to have received command ${o} ${a} times, but received ${r.length} times`}},toHaveReceivedCommandWith(t,c,a){const r=$(t).filter(n=>n[0]instanceof c),m=r.some(n=>this.equals(n[0].input,a)),o=c.name;return{pass:m,message:()=>{if(m){const s=e.red(o),u=e.cyan(JSON.stringify(a,void 0,2));return`Expected AWS SDK mock not to have received command ${s} with input:
2
+ ${u}`}if(r.length===0){const s=e.red(o),u=e.cyan(JSON.stringify(a,void 0,2)),i=e.gray(`${o} was never called`);return`Expected AWS SDK mock to have received command ${s} with input:
3
+ ${u}
4
+
5
+ But ${i}`}const n=r.map(s=>s[0].input),d=e.red(o),h=e.cyan(JSON.stringify(a,void 0,2)),l=e.gray("But received"),g=e.yellow(o),S=e.gray("with"),p=e.yellow(n.map(s=>JSON.stringify(s,void 0,2)).join(`
6
+
7
+ `));return`Expected AWS SDK mock to have received command ${d} with input:
8
+ ${h}
9
+
10
+ ${l} ${g} ${S}:
11
+ ${p}`}}},toHaveReceivedNthCommandWith(t,c,a,r){const m=$(t),o=m[c-1],n=o?.[0],d=n?.input,h=!!o&&n instanceof a&&this.equals(d,r),l=a.name;return{pass:h,message:()=>{if(h){const i=e.magenta(c.toString()),v=e.red(l),y=e.cyan(JSON.stringify(r,void 0,2));return`Expected AWS SDK mock not to have received nth (${i}) command ${v} with input:
12
+ ${y}`}if(!o){const i=e.magenta(c.toString()),v=e.gray(`only received ${m.length} call(s)`);return`Expected AWS SDK mock to have received at least ${i} call(s), but ${v}`}if(!(n instanceof a)){const i=n?.constructor?.name??typeof n,v=e.magenta(c.toString()),y=e.red(l),x=e.yellow(i);return`Expected AWS SDK mock nth (${v}) call to be ${y}, but received ${x}`}const g=e.magenta(c.toString()),S=e.red(l),p=e.cyan(JSON.stringify(r,void 0,2)),s=e.gray("But received"),u=e.yellow(JSON.stringify(d,void 0,2));return`Expected AWS SDK mock nth (${g}) command ${S} with input:
13
+ ${p}
14
+
15
+ ${s}:
16
+ ${u}`}}},toHaveReceivedNoOtherCommands(t,c=[]){const r=$(t).filter(o=>{const n=o[0];return!c.some(d=>n instanceof d)}),m=r.length===0;return{pass:m,message:()=>{if(m)return`Expected AWS SDK mock to have received other commands besides ${e.green(c.map(n=>n.name).join(", "))}`;const o=r.map(n=>n[0].constructor?.name??"Unknown");return`Expected AWS SDK mock to have received ${e.gray("no other commands")}, but received: ${e.red(o.join(", "))}`}}}};exports.matchers=f;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-sdk-vitest-mock",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
package/vitest-setup.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";const e=require("vitest"),t=require("./matchers-D1XEjFLq.cjs");e.expect.extend(t.matchers);
1
+ "use strict";const e=require("vitest"),t=require("./matchers-G36N2DNa.cjs");e.expect.extend(t.matchers);
package/vitest-setup.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { expect as m } from "vitest";
2
- import { m as e } from "./matchers-DclpcT4f.js";
2
+ import { m as e } from "./matchers-DdPGAOsD.js";
3
3
  m.extend(e);
@@ -1 +0,0 @@
1
- "use strict";const d={toHaveReceivedCommand(n,e){const t=n.calls().some(a=>a[0]instanceof e),s=e.name;return{pass:t,message:()=>t?`Expected AWS SDK mock not to have received command ${s}`:`Expected AWS SDK mock to have received command ${s}`}},toHaveReceivedCommandTimes(n,e,c){const t=n.calls().filter(o=>o[0]instanceof e),s=t.length===c,a=e.name;return{pass:s,message:()=>s?`Expected AWS SDK mock not to have received command ${a} ${c} times`:`Expected AWS SDK mock to have received command ${a} ${c} times, but received ${t.length} times`}},toHaveReceivedCommandWith(n,e,c){const s=n.calls().filter(o=>o[0]instanceof e).some(o=>this.equals(o[0].input,c)),a=e.name;return{pass:s,message:()=>s?`Expected AWS SDK mock not to have received command ${a} with ${JSON.stringify(c)}`:`Expected AWS SDK mock to have received command ${a} with ${JSON.stringify(c)}`}},toHaveReceivedNthCommandWith(n,e,c,t){const a=n.calls().filter(i=>i[0]instanceof c)[e-1],o=!!(a&&this.equals(a[0].input,t)),m=c.name;return{pass:o,message:()=>o?`Expected AWS SDK mock not to have received nth (${e}) command ${m} with ${JSON.stringify(t)}`:`Expected AWS SDK mock to have received nth (${e}) command ${m} with ${JSON.stringify(t)}`}}};exports.matchers=d;
@@ -1,33 +0,0 @@
1
- const d = {
2
- toHaveReceivedCommand(n, e) {
3
- const t = n.calls().some((a) => a[0] instanceof e), o = e.name;
4
- return {
5
- pass: t,
6
- message: () => t ? `Expected AWS SDK mock not to have received command ${o}` : `Expected AWS SDK mock to have received command ${o}`
7
- };
8
- },
9
- toHaveReceivedCommandTimes(n, e, c) {
10
- const t = n.calls().filter((s) => s[0] instanceof e), o = t.length === c, a = e.name;
11
- return {
12
- pass: o,
13
- message: () => o ? `Expected AWS SDK mock not to have received command ${a} ${c} times` : `Expected AWS SDK mock to have received command ${a} ${c} times, but received ${t.length} times`
14
- };
15
- },
16
- toHaveReceivedCommandWith(n, e, c) {
17
- const o = n.calls().filter((s) => s[0] instanceof e).some((s) => this.equals(s[0].input, c)), a = e.name;
18
- return {
19
- pass: o,
20
- message: () => o ? `Expected AWS SDK mock not to have received command ${a} with ${JSON.stringify(c)}` : `Expected AWS SDK mock to have received command ${a} with ${JSON.stringify(c)}`
21
- };
22
- },
23
- toHaveReceivedNthCommandWith(n, e, c, t) {
24
- const a = n.calls().filter((i) => i[0] instanceof c)[e - 1], s = !!(a && this.equals(a[0].input, t)), m = c.name;
25
- return {
26
- pass: s,
27
- message: () => s ? `Expected AWS SDK mock not to have received nth (${e}) command ${m} with ${JSON.stringify(t)}` : `Expected AWS SDK mock to have received nth (${e}) command ${m} with ${JSON.stringify(t)}`
28
- };
29
- }
30
- };
31
- export {
32
- d as m
33
- };