aws-sdk-vitest-mock 0.3.0 → 0.4.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 CHANGED
@@ -43,6 +43,8 @@ pnpm add -D aws-sdk-vitest-mock
43
43
 
44
44
  ### Basic Usage
45
45
 
46
+ > **Note:** `mockClient()` mocks **all instances** of a client class. Use `mockClientInstance()` when you need to mock a specific instance.
47
+
46
48
  ```typescript
47
49
  import { describe, test, expect, beforeEach, afterEach } from "vitest";
48
50
  import { mockClient } from "aws-sdk-vitest-mock";
@@ -65,10 +67,10 @@ describe("DocumentService", () => {
65
67
  let documentService: DocumentService;
66
68
 
67
69
  beforeEach(() => {
68
- // Mock the S3 client
70
+ // Mock all instances of S3Client
69
71
  s3Mock = mockClient(S3Client);
70
72
 
71
- // Create service with real S3Client (which is now mocked)
73
+ // Any S3Client instance created after this will be mocked
72
74
  const s3Client = new S3Client({ region: "us-east-1" });
73
75
  documentService = new DocumentService(s3Client);
74
76
  });
@@ -93,6 +95,18 @@ describe("DocumentService", () => {
93
95
  });
94
96
  ```
95
97
 
98
+ ## 🎯 Key Concepts
99
+
100
+ Understanding these concepts will help you use the library effectively:
101
+
102
+ - **`mockClient(ClientClass)`** - Mocks **all instances** of a client class. Use this in most test scenarios where you control client creation.
103
+ - **`mockClientInstance(instance)`** - Mocks a **specific client instance**. Use when the client is created outside your test (e.g., in application bootstrap).
104
+ - **Command Matching** - Commands are matched by constructor. Optionally match by input properties (partial matching by default, strict matching available).
105
+ - **Sequential Responses** - Use `resolvesOnce()` / `rejectsOnce()` for one-time behaviors that fall back to permanent handlers set with `resolves()` / `rejects()`.
106
+ - **Chainable API** - All mock configuration methods return the stub, allowing method chaining for cleaner test setup.
107
+
108
+ ## 📖 Usage Guide
109
+
96
110
  ### Request Matching
97
111
 
98
112
  ```typescript
@@ -121,56 +135,66 @@ s3Mock
121
135
  // All other calls return 'subsequent calls'
122
136
  ```
123
137
 
124
- ### Fixture Loading
125
-
126
- Load mock responses from files for easier test data management:
138
+ ### Error Handling
127
139
 
128
140
  ```typescript
129
- // Load JSON response from file
130
- s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/s3-response.json");
141
+ s3Mock.on(GetObjectCommand).rejects(new Error("Not found"));
131
142
 
132
- // Load text response from file
133
- s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/response.txt");
143
+ // Or with rejectsOnce
144
+ s3Mock
145
+ .on(GetObjectCommand)
146
+ .rejectsOnce(new Error("Temporary failure"))
147
+ .resolves({ Body: "success" });
148
+ ```
149
+
150
+ ### Custom Handlers
134
151
 
135
- // JSON files are automatically parsed, text files returned as strings
136
- // File paths are resolved relative to current working directory
152
+ ```typescript
153
+ s3Mock.on(GetObjectCommand).callsFake(async (input, getClient) => {
154
+ const client = getClient();
155
+ console.log("Bucket:", input.Bucket);
156
+ return { Body: `Dynamic response for ${input.Key}` };
157
+ });
137
158
  ```
138
159
 
139
- ### Paginator Support
160
+ ### Mocking Existing Instances
140
161
 
141
- Mock AWS SDK v3 pagination with automatic token handling:
162
+ Use `mockClientInstance()` when you need to mock a client that's already been created:
142
163
 
143
164
  ```typescript
144
- // Mock DynamoDB scan with pagination
145
- const items = Array.from({ length: 25 }, (_, i) => ({
146
- id: { S: `item-${i + 1}` },
147
- }));
165
+ // Your application service that uses an injected S3 client
166
+ class FileUploadService {
167
+ constructor(private s3Client: S3Client) {}
148
168
 
149
- dynamoMock.on(ScanCommand).resolvesPaginated(items, {
150
- pageSize: 10,
151
- itemsKey: "Items",
152
- tokenKey: "NextToken",
153
- });
169
+ async uploadFile(bucket: string, key: string, data: string) {
170
+ return await this.s3Client.send(
171
+ new PutObjectCommand({ Bucket: bucket, Key: key, Body: data }),
172
+ );
173
+ }
174
+ }
154
175
 
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
176
+ test("should mock existing S3 client instance", async () => {
177
+ // Client is already created (e.g., in application bootstrap)
178
+ const s3Client = new S3Client({ region: "us-east-1" });
179
+ const service = new FileUploadService(s3Client);
158
180
 
159
- // Mock S3 list objects with pagination
160
- const objects = Array.from({ length: 15 }, (_, i) => ({
161
- Key: `file-${i + 1}.txt`,
162
- }));
181
+ // Mock the specific client instance
182
+ const mock = mockClientInstance(s3Client);
183
+ mock.on(PutObjectCommand).resolves({ ETag: "mock-etag" });
163
184
 
164
- s3Mock.on(ListObjectsV2Command).resolvesPaginated(objects, {
165
- pageSize: 10,
166
- itemsKey: "Contents",
167
- tokenKey: "ContinuationToken",
185
+ // Test your service
186
+ const result = await service.uploadFile("bucket", "key", "data");
187
+
188
+ expect(result.ETag).toBe("mock-etag");
189
+ expect(mock).toHaveReceivedCommand(PutObjectCommand);
168
190
  });
169
191
  ```
170
192
 
193
+ ## 🔧 AWS Service Examples
194
+
171
195
  ### DynamoDB with Marshal/Unmarshal
172
196
 
173
- Mock DynamoDB operations using AWS SDK's marshal/unmarshal utilities for type-safe data handling:
197
+ Mock DynamoDB operations using marshal/unmarshal utilities for type-safe data handling:
174
198
 
175
199
  ```typescript
176
200
  import { describe, test, expect, beforeEach, afterEach } from "vitest";
@@ -265,7 +289,9 @@ describe("UserService with DynamoDB", () => {
265
289
  });
266
290
  ```
267
291
 
268
- ### Stream Mocking (S3 Helper)
292
+ ## 🚀 Advanced Features
293
+
294
+ ### Stream Mocking (S3)
269
295
 
270
296
  Mock S3 operations that return streams with automatic environment detection:
271
297
 
@@ -283,21 +309,70 @@ s3Mock
283
309
  .resolvesStream("Subsequent calls");
284
310
  ```
285
311
 
286
- ### Delay/Latency Simulation
312
+ ### Paginator Support
287
313
 
288
- Simulate network delays for testing timeouts and race conditions:
314
+ Mock AWS SDK v3 pagination with automatic token handling:
289
315
 
290
316
  ```typescript
291
- // Resolve with delay
292
- s3Mock.on(GetObjectCommand).resolvesWithDelay({ Body: "data" }, 1000);
317
+ // Mock DynamoDB scan with pagination
318
+ // DynamoDB uses different keys for input (ExclusiveStartKey) and output (LastEvaluatedKey)
319
+ const items = Array.from({ length: 25 }, (_, i) => ({
320
+ id: { S: `item-${i + 1}` },
321
+ }));
293
322
 
294
- // Reject with delay
295
- s3Mock.on(GetObjectCommand).rejectsWithDelay("Network timeout", 500);
323
+ dynamoMock.on(ScanCommand).resolvesPaginated(items, {
324
+ pageSize: 10,
325
+ itemsKey: "Items",
326
+ tokenKey: "LastEvaluatedKey", // Token key in response
327
+ inputTokenKey: "ExclusiveStartKey", // Token key in request (when different from response)
328
+ });
329
+
330
+ // First call returns items 1-10 with LastEvaluatedKey
331
+ const result1 = await client.send(new ScanCommand({ TableName: "test" }));
332
+ // result1.LastEvaluatedKey = "token-10"
333
+
334
+ // Second call with ExclusiveStartKey returns items 11-20
335
+ const result2 = await client.send(
336
+ new ScanCommand({
337
+ TableName: "test",
338
+ ExclusiveStartKey: result1.LastEvaluatedKey,
339
+ }),
340
+ );
341
+ // result2.LastEvaluatedKey = "token-20"
342
+
343
+ // Third call returns items 21-25 without LastEvaluatedKey
344
+ const result3 = await client.send(
345
+ new ScanCommand({
346
+ TableName: "test",
347
+ ExclusiveStartKey: result2.LastEvaluatedKey,
348
+ }),
349
+ );
350
+ // result3.LastEvaluatedKey = undefined (no more pages)
351
+
352
+ // Mock S3 list objects with pagination
353
+ // S3 uses the same key for input and output, so inputTokenKey is optional
354
+ const objects = Array.from({ length: 15 }, (_, i) => ({
355
+ Key: `file-${i + 1}.txt`,
356
+ }));
357
+
358
+ s3Mock.on(ListObjectsV2Command).resolvesPaginated(objects, {
359
+ pageSize: 10,
360
+ itemsKey: "Contents",
361
+ tokenKey: "NextContinuationToken", // Used for both input and output
362
+ });
296
363
  ```
297
364
 
365
+ **Pagination Options:**
366
+
367
+ - `pageSize` - Number of items per page (default: 10)
368
+ - `itemsKey` - Property name for items array in response (default: "Items")
369
+ - `tokenKey` - Property name for pagination token in response (default: "NextToken")
370
+ - `inputTokenKey` - Property name for pagination token in request (default: same as `tokenKey`)
371
+ - Use this when AWS service uses different names for input/output tokens (e.g., DynamoDB's `ExclusiveStartKey` vs `LastEvaluatedKey`)
372
+
298
373
  ### AWS Error Simulation
299
374
 
300
- Convenient methods for common AWS errors:
375
+ Convenient helper methods for common AWS errors:
301
376
 
302
377
  ```typescript
303
378
  // S3 Errors
@@ -314,61 +389,34 @@ s3Mock.on(GetObjectCommand).rejectsWithThrottling();
314
389
  s3Mock.on(GetObjectCommand).rejectsWithInternalServerError();
315
390
  ```
316
391
 
317
- ### Error Handling
392
+ ### Delay/Latency Simulation
393
+
394
+ Simulate network delays for testing timeouts and race conditions:
318
395
 
319
396
  ```typescript
320
- s3Mock.on(GetObjectCommand).rejects(new Error("Not found"));
397
+ // Resolve with delay
398
+ s3Mock.on(GetObjectCommand).resolvesWithDelay({ Body: "data" }, 1000);
321
399
 
322
- // Or with rejectsOnce
323
- s3Mock
324
- .on(GetObjectCommand)
325
- .rejectsOnce(new Error("Temporary failure"))
326
- .resolves({ Body: "success" });
400
+ // Reject with delay
401
+ s3Mock.on(GetObjectCommand).rejectsWithDelay("Network timeout", 500);
327
402
  ```
328
403
 
329
- ### Custom Handlers
330
-
331
- ```typescript
332
- s3Mock.on(GetObjectCommand).callsFake(async (input, getClient) => {
333
- const client = getClient();
334
- console.log("Bucket:", input.Bucket);
335
- return { Body: `Dynamic response for ${input.Key}` };
336
- });
337
- ```
404
+ ### Fixture Loading
338
405
 
339
- ### Mocking Existing Instances
406
+ Load mock responses from files for easier test data management:
340
407
 
341
408
  ```typescript
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");
409
+ // Load JSON response from file (automatically parsed)
410
+ s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/s3-response.json");
364
411
 
365
- expect(result.ETag).toBe("mock-etag");
366
- expect(mock).toHaveReceivedCommand(PutObjectCommand);
367
- });
412
+ // Load text response from file (returned as string)
413
+ s3Mock.on(GetObjectCommand).resolvesFromFile("./fixtures/response.txt");
368
414
  ```
369
415
 
370
416
  ### Debug Mode
371
417
 
418
+ Enable debug logging to troubleshoot mock configurations when they're not matching as expected:
419
+
372
420
  Enable debug logging to troubleshoot mock configurations and see detailed information about command matching:
373
421
 
374
422
  ```typescript
@@ -543,9 +591,11 @@ bun nx build
543
591
 
544
592
  See [CONTRIBUTING.md](./CONTRIBUTING.md) for the complete guide.
545
593
 
546
- ## 🙏 Acknowledgements
594
+ ## Acknowledgements
595
+
596
+ This library is based on the core ideas and API patterns introduced by [aws-sdk-client-mock](https://github.com/m-radzikowski/aws-sdk-client-mock), which is no longer actively maintained.
547
597
 
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.
598
+ It reimagines those concepts for Vitest, while extending them with additional features, improved ergonomics, and ongoing maintenance.
549
599
 
550
600
  ## 📝 License
551
601
 
package/index.cjs CHANGED
@@ -1 +1 @@
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"),C=require("./matchers-Rq18z2C7.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 W=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 h(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:h(r)}),!1),o},resolvesStreamOnce(r){return n(()=>Promise.resolve({Body:h(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(W(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 m=i[l]||i[i.length-1]||i[0];if(!m)throw new Error("No paginated responses available");return l=Math.min(l+1,i.length-1),Promise.resolve(m)},!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.map(o=>o[0]),__rawCalls:()=>c.mock.calls,enableDebug:()=>{y(e.debugLogger)},disableDebug:()=>{v(e.debugLogger)}}},_=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.map(n=>n[0]),__rawCalls:()=>s.mock.calls,enableDebug:()=>{y(e.debugLogger)},disableDebug:()=>{v(e.debugLogger)}}};exports.matchers=C.matchers;exports.mockClient=B;exports.mockClientInstance=_;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("vitest"),R=require("node:fs"),N=require("node:path"),C=require("node:stream"),W=require("./matchers-Rq18z2C7.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 M=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)},T=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),F=()=>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 L(t){const e=N.resolve(t),s=R.readFileSync(e,"utf8");return t.endsWith(".json")?JSON.parse(s):s}function K(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 q(t){const e=typeof t=="string"?Buffer.from(t,"utf8"):Buffer.from(t);let s=!1;return new C.Readable({read(){s||(this.push(e),this.push(null),s=!0)}})}function B(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 h(t){const e=A();return e==="node"||e==="bun"?q(t):B(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:h(r)}),!1),o},resolvesStreamOnce(r){return n(()=>Promise.resolve({Body:h(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(M(r)),!1),o},rejectsWithNoSuchBucket(r){return n(()=>Promise.reject(T(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(F()),!1),o},resolvesPaginated(r,u={}){const i=K(r,u);let l=0;return n(a=>{const f=u.tokenKey||"NextToken",E=u.inputTokenKey||f,p=a[E];if(p){const g=/token-(\d+)/.exec(p);if(g&&g[1]){const P=g[1];l=Math.floor(Number.parseInt(P,10)/(u.pageSize||10))}}else l=0;const m=i[l]||i[i.length-1]||i[0];if(!m)throw new Error("No paginated responses available");return l=Math.min(l+1,i.length-1),Promise.resolve(m)},!1),o},resolvesFromFile(r){return n(()=>{const u=L(r);return Promise.resolve(u)},!1),o}};return o}const _=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.map(o=>o[0]),__rawCalls:()=>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.map(n=>n[0]),__rawCalls:()=>s.mock.calls,enableDebug:()=>{y(e.debugLogger)},disableDebug:()=>{v(e.debugLogger)}}};exports.matchers=W.matchers;exports.mockClient=_;exports.mockClientInstance=V;
package/index.js CHANGED
@@ -1,26 +1,26 @@
1
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-C6AtmwWz.js";
2
+ import { readFileSync as R } from "node:fs";
3
+ import N from "node:path";
4
+ import { Readable as W } from "node:stream";
5
+ import { m as Y } from "./matchers-C6AtmwWz.js";
6
6
  class d extends Error {
7
7
  constructor(e, s, c, n) {
8
8
  super(e), this.name = "AwsError", this.code = s, this.statusCode = c, this.retryable = n;
9
9
  }
10
10
  }
11
- const W = (t) => {
11
+ const C = (t) => {
12
12
  const e = t ? `The specified key does not exist. Key: ${t}` : "The specified key does not exist.";
13
13
  return new d(e, "NoSuchKey", 404, !1);
14
- }, C = (t) => {
14
+ }, $ = (t) => {
15
15
  const e = t ? `The specified bucket does not exist. Bucket: ${t}` : "The specified bucket does not exist.";
16
16
  return new d(e, "NoSuchBucket", 404, !1);
17
- }, $ = (t) => {
17
+ }, D = (t) => {
18
18
  const e = t ? `Access Denied for resource: ${t}` : "Access Denied";
19
19
  return new d(e, "AccessDenied", 403, !1);
20
- }, D = (t) => {
20
+ }, M = (t) => {
21
21
  const e = t ? `Requested resource not found: ${t}` : "Requested resource not found";
22
22
  return new d(e, "ResourceNotFoundException", 400, !1);
23
- }, M = () => new d(
23
+ }, T = () => new d(
24
24
  "The conditional request failed",
25
25
  "ConditionalCheckFailedException",
26
26
  400,
@@ -45,11 +45,11 @@ function y(t) {
45
45
  function w(t) {
46
46
  t.enabled = !1;
47
47
  }
48
- function T(t) {
49
- const e = R.resolve(t), s = P(e, "utf8");
48
+ function F(t) {
49
+ const e = N.resolve(t), s = R(e, "utf8");
50
50
  return t.endsWith(".json") ? JSON.parse(s) : s;
51
51
  }
52
- function F(t, e = {}) {
52
+ function L(t, e = {}) {
53
53
  const { pageSize: s = 10, tokenKey: c = "NextToken", itemsKey: n = "Items" } = e;
54
54
  if (t.length === 0)
55
55
  return [{ [n]: [] }];
@@ -64,18 +64,19 @@ function F(t, e = {}) {
64
64
  }
65
65
  return o;
66
66
  }
67
- function L() {
67
+ function K() {
68
68
  return typeof process < "u" && process.versions?.node ? "node" : typeof process < "u" && process.versions?.bun ? "bun" : "browser";
69
69
  }
70
70
  function A(t) {
71
71
  const e = typeof t == "string" ? Buffer.from(t, "utf8") : Buffer.from(t);
72
- return new N({
72
+ let s = !1;
73
+ return new W({
73
74
  read() {
74
- this.push(e), this.push(void 0);
75
+ s || (this.push(e), this.push(null), s = !0);
75
76
  }
76
77
  });
77
78
  }
78
- function K(t) {
79
+ function B(t) {
79
80
  let e;
80
81
  return typeof t == "string" ? e = new TextEncoder().encode(t) : t instanceof Buffer ? e = new Uint8Array(t) : e = t, new ReadableStream({
81
82
  start(s) {
@@ -84,8 +85,8 @@ function K(t) {
84
85
  });
85
86
  }
86
87
  function h(t) {
87
- const e = L();
88
- return e === "node" || e === "bun" ? A(t) : K(t);
88
+ const e = K();
89
+ return e === "node" || e === "bun" ? A(t) : B(t);
89
90
  }
90
91
  function v(t, e) {
91
92
  return Object.keys(e).every((s) => {
@@ -222,23 +223,23 @@ function S(t, e, s, c = {}) {
222
223
  return n(() => new Promise(l), !1), o;
223
224
  },
224
225
  rejectsWithNoSuchKey(r) {
225
- return n(() => Promise.reject(W(r)), !1), o;
226
+ return n(() => Promise.reject(C(r)), !1), o;
226
227
  },
227
228
  rejectsWithNoSuchBucket(r) {
228
- return n(() => Promise.reject(C(r)), !1), o;
229
+ return n(() => Promise.reject($(r)), !1), o;
229
230
  },
230
231
  rejectsWithAccessDenied(r) {
231
- return n(() => Promise.reject($(r)), !1), o;
232
+ return n(() => Promise.reject(D(r)), !1), o;
232
233
  },
233
234
  rejectsWithResourceNotFound(r) {
234
235
  return n(
235
- () => Promise.reject(D(r)),
236
+ () => Promise.reject(M(r)),
236
237
  !1
237
238
  ), o;
238
239
  },
239
240
  rejectsWithConditionalCheckFailed() {
240
241
  return n(
241
- () => Promise.reject(M()),
242
+ () => Promise.reject(T()),
242
243
  !1
243
244
  ), o;
244
245
  },
@@ -249,16 +250,16 @@ function S(t, e, s, c = {}) {
249
250
  return n(() => Promise.reject(O()), !1), o;
250
251
  },
251
252
  resolvesPaginated(r, u = {}) {
252
- const i = F(r, u);
253
+ const i = L(r, u);
253
254
  let l = 0;
254
255
  return n((a) => {
255
- const f = u.tokenKey || "NextToken", p = a[f];
256
+ const f = u.tokenKey || "NextToken", E = u.inputTokenKey || f, p = a[E];
256
257
  if (p) {
257
258
  const g = /token-(\d+)/.exec(p);
258
259
  if (g && g[1]) {
259
- const E = g[1];
260
+ const P = g[1];
260
261
  l = Math.floor(
261
- Number.parseInt(E, 10) / (u.pageSize || 10)
262
+ Number.parseInt(P, 10) / (u.pageSize || 10)
262
263
  );
263
264
  }
264
265
  } else
@@ -275,14 +276,14 @@ function S(t, e, s, c = {}) {
275
276
  },
276
277
  resolvesFromFile(r) {
277
278
  return n(() => {
278
- const u = T(r);
279
+ const u = F(r);
279
280
  return Promise.resolve(u);
280
281
  }, !1), o;
281
282
  }
282
283
  };
283
284
  return o;
284
285
  }
285
- const U = (t) => {
286
+ const G = (t) => {
286
287
  const e = {
287
288
  map: /* @__PURE__ */ new WeakMap(),
288
289
  debugLogger: k()
@@ -310,7 +311,7 @@ const U = (t) => {
310
311
  w(e.debugLogger);
311
312
  }
312
313
  };
313
- }, G = (t) => {
314
+ }, H = (t) => {
314
315
  const e = {
315
316
  map: /* @__PURE__ */ new WeakMap(),
316
317
  debugLogger: k()
@@ -340,7 +341,7 @@ const U = (t) => {
340
341
  };
341
342
  };
342
343
  export {
343
- X as matchers,
344
- U as mockClient,
345
- G as mockClientInstance
344
+ Y as matchers,
345
+ G as mockClient,
346
+ H as mockClientInstance
346
347
  };
@@ -1,6 +1,7 @@
1
1
  export interface PaginatorOptions {
2
2
  pageSize?: number;
3
3
  tokenKey?: string;
4
+ inputTokenKey?: string;
4
5
  itemsKey?: string;
5
6
  }
6
7
  export interface PaginatedResponse<T = unknown> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aws-sdk-vitest-mock",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",