@backend-master/test-utils 1.0.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 ADDED
@@ -0,0 +1,250 @@
1
+ # @backend-master/test-utils
2
+
3
+ A comprehensive testing utilities library for Node.js backend projects. Provides mocks, fixtures, spies, and assertion helpers to make testing easier and faster.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @backend-master/test-utils
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Mock Database**: In-memory database for testing without external dependencies
14
+ - **HTTP Helpers**: Build and assert HTTP responses easily
15
+ - **Fixtures**: Generate realistic test data with customizable builders
16
+ - **Spies & Mocks**: Track function calls and create mock objects
17
+ - **TypeScript Support**: Full type safety and IDE autocomplete
18
+
19
+ ## Quick Start
20
+
21
+ ### Mock Database
22
+
23
+ ```typescript
24
+ import { MockDatabase } from '@backend-master/test-utils';
25
+
26
+ const db = new MockDatabase();
27
+ db.createTable('users');
28
+
29
+ const user = db.insert('users', {
30
+ name: 'Alice',
31
+ email: 'alice@example.com'
32
+ });
33
+
34
+ const found = db.findOne('users', { email: 'alice@example.com' });
35
+ expect(found).toEqual(user);
36
+ ```
37
+
38
+ ### HTTP Test Helpers
39
+
40
+ ```typescript
41
+ import { HttpTestBuilder, HttpResponseAssertion } from '@backend-master/test-utils';
42
+
43
+ // Create responses
44
+ const response = HttpTestBuilder.ok({ userId: 1, name: 'Alice' });
45
+
46
+ // Assert responses
47
+ HttpResponseAssertion.assertStatus(response, 200);
48
+ HttpResponseAssertion.assertBodyHasKey(response, 'userId');
49
+ HttpResponseAssertion.assertSuccess(response);
50
+ ```
51
+
52
+ ### Test Fixtures
53
+
54
+ ```typescript
55
+ import { Fixtures, FixtureBuilder } from '@backend-master/test-utils';
56
+
57
+ // Quick fixtures
58
+ const user = Fixtures.user({ role: 'admin' });
59
+ const products = Fixtures.products(10);
60
+
61
+ // Custom builder
62
+ const builder = new FixtureBuilder({ name: 'Test', active: true });
63
+ const items = builder
64
+ .set('name', 'Updated Name')
65
+ .merge({ active: false })
66
+ .buildMany(5);
67
+ ```
68
+
69
+ ### Spies & Mocks
70
+
71
+ ```typescript
72
+ import { Spy, MockObject, Defer } from '@backend-master/test-utils';
73
+
74
+ // Create a spy
75
+ const spy = Spy.create().returnValue('result');
76
+ spy(); // Call it
77
+ expect(spy.wasCalled()).toBe(true);
78
+ expect(spy.getCallCount()).toBe(1);
79
+
80
+ // Mock an object
81
+ const mockService = MockObject.create({
82
+ fetch: async () => ({ data: 'test' })
83
+ });
84
+
85
+ mockService.fetch();
86
+ expect(mockService.fetch.wasCalled()).toBe(true);
87
+
88
+ // Defer for async testing
89
+ const defer = new Defer();
90
+ defer.resolve('value');
91
+ await expect(defer.promise).resolves.toBe('value');
92
+ ```
93
+
94
+ ## API Reference
95
+
96
+ ### MockDatabase
97
+
98
+ #### Methods
99
+
100
+ - `createTable(name, schema?)` - Create a new table
101
+ - `insert(table, record)` - Insert a record
102
+ - `find(table, query)` - Find matching records
103
+ - `findOne(table, query)` - Find first matching record
104
+ - `update(table, query, updates)` - Update matching records
105
+ - `delete(table, query)` - Delete matching records
106
+ - `getAll(table)` - Get all records
107
+ - `clearTable(table)` - Clear a table
108
+ - `clearAll()` - Clear all tables
109
+ - `count(table)` - Count records in table
110
+
111
+ ### HttpTestBuilder
112
+
113
+ Static methods to create HTTP responses:
114
+
115
+ - `ok(body)` - 200 response
116
+ - `created(body)` - 201 response
117
+ - `badRequest(message)` - 400 response
118
+ - `unauthorized(message)` - 401 response
119
+ - `forbidden(message)` - 403 response
120
+ - `notFound(message)` - 404 response
121
+ - `serverError(message)` - 500 response
122
+ - `custom(status, body, headers)` - Custom response
123
+
124
+ ### HttpResponseAssertion
125
+
126
+ Methods to assert HTTP responses:
127
+
128
+ - `assertStatus(response, status)` - Check status code
129
+ - `assertBodyHasKey(response, key)` - Check body contains key
130
+ - `assertBodyMatches(response, expected)` - Check body values
131
+ - `assertHeaderExists(response, name)` - Check header exists
132
+ - `assertSuccess(response)` - Check 2xx status
133
+ - `assertError(response)` - Check 4xx/5xx status
134
+
135
+ ### Fixtures
136
+
137
+ Pre-built fixture generators:
138
+
139
+ - `user(overrides?)` - Generate a user
140
+ - `users(count, overrides?)` - Generate multiple users
141
+ - `product(overrides?)` - Generate a product
142
+ - `products(count, overrides?)` - Generate multiple products
143
+ - `order(overrides?)` - Generate an order
144
+ - `orders(count, overrides?)` - Generate multiple orders
145
+ - `email(domain?)` - Generate a random email
146
+ - `randomString(length?)` - Generate random string
147
+ - `uuid()` - Generate a UUID
148
+ - `custom(data, count?)` - Create custom fixture
149
+
150
+ ### FixtureBuilder<T>
151
+
152
+ Builder pattern for complex fixtures:
153
+
154
+ - `set(key, value)` - Set a property
155
+ - `merge(overrides)` - Merge properties
156
+ - `build()` - Build single fixture
157
+ - `buildMany(count)` - Build multiple fixtures
158
+
159
+ ### Spy
160
+
161
+ Function spy for tracking calls:
162
+
163
+ - `create(implementation?)` - Create a spy
164
+ - `returnValue(value)` - Set return value
165
+ - `throwError(error)` - Make it throw
166
+ - `getCalls()` - Get all calls
167
+ - `getCallCount()` - Get call count
168
+ - `getArguments(index)` - Get call arguments
169
+ - `getFirstArg()` - Get first arg of first call
170
+ - `wasCalled()` - Check if called
171
+ - `wasCalledWith(...args)` - Check arguments
172
+ - `reset()` - Reset spy
173
+
174
+ ### MockObject
175
+
176
+ Object mocking utilities:
177
+
178
+ - `create(shape)` - Create object with properties
179
+ - `createWithSpies(keys)` - Create object with spy methods
180
+
181
+ ### Defer<T>
182
+
183
+ Promise deferred for testing async code:
184
+
185
+ - `promise` - The promise
186
+ - `resolve(value)` - Resolve the promise
187
+ - `reject(reason)` - Reject the promise
188
+
189
+ ## Usage Examples
190
+
191
+ ### Testing a User Service
192
+
193
+ ```typescript
194
+ import { MockDatabase, Fixtures, Spy } from '@backend-master/test-utils';
195
+
196
+ describe('UserService', () => {
197
+ let db: MockDatabase;
198
+ let emailService: any;
199
+
200
+ beforeEach(() => {
201
+ db = new MockDatabase();
202
+ db.createTable('users');
203
+ emailService = { send: Spy.create() };
204
+ });
205
+
206
+ test('should create user and send email', () => {
207
+ const user = Fixtures.user({ email: 'new@example.com' });
208
+ db.insert('users', user);
209
+ emailService.send();
210
+
211
+ expect(db.count('users')).toBe(1);
212
+ expect(emailService.send.wasCalled()).toBe(true);
213
+ });
214
+ });
215
+ ```
216
+
217
+ ### Testing an API Endpoint
218
+
219
+ ```typescript
220
+ import { HttpTestBuilder, HttpResponseAssertion } from '@backend-master/test-utils';
221
+
222
+ describe('GET /api/users/:id', () => {
223
+ test('should return user when found', () => {
224
+ const response = HttpTestBuilder.ok({
225
+ id: 1,
226
+ name: 'Alice'
227
+ });
228
+
229
+ HttpResponseAssertion.assertStatus(response, 200);
230
+ HttpResponseAssertion.assertBodyHasKey(response, 'name');
231
+ });
232
+
233
+ test('should return 404 when not found', () => {
234
+ const response = HttpTestBuilder.notFound('User not found');
235
+ HttpResponseAssertion.assertStatus(response, 404);
236
+ });
237
+ });
238
+ ```
239
+
240
+ ## Best Practices
241
+
242
+ 1. **Use Fixtures for Realistic Data**: Always use fixtures instead of hardcoding test data
243
+ 2. **Separate Concerns**: Keep database mocks, HTTP mocks, and service mocks separate
244
+ 3. **Reset State**: Always reset spies and databases between tests
245
+ 4. **Type Safety**: Leverage TypeScript types for better IDE support
246
+ 5. **Readable Assertions**: Use assertion helpers instead of raw comparisons
247
+
248
+ ## License
249
+
250
+ MIT
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Test Fixtures & Data Generators
3
+ * Utilities for creating realistic test data
4
+ */
5
+ export interface FixtureGeneratorOptions {
6
+ count?: number;
7
+ seed?: number;
8
+ }
9
+ export declare class Fixtures {
10
+ /**
11
+ * Generate a user fixture
12
+ */
13
+ static user(overrides?: Record<string, any>): any;
14
+ /**
15
+ * Generate multiple user fixtures
16
+ */
17
+ static users(count?: number, overrides?: Record<string, any>): any[];
18
+ /**
19
+ * Generate a product fixture
20
+ */
21
+ static product(overrides?: Record<string, any>): any;
22
+ /**
23
+ * Generate multiple product fixtures
24
+ */
25
+ static products(count?: number, overrides?: Record<string, any>): any[];
26
+ /**
27
+ * Generate an order fixture
28
+ */
29
+ static order(overrides?: Record<string, any>): any;
30
+ /**
31
+ * Generate multiple order fixtures
32
+ */
33
+ static orders(count?: number, overrides?: Record<string, any>): any[];
34
+ /**
35
+ * Generate a random email
36
+ */
37
+ static email(domain?: string): string;
38
+ /**
39
+ * Generate a random string
40
+ */
41
+ static randomString(length?: number): string;
42
+ /**
43
+ * Generate a random UUID
44
+ */
45
+ static uuid(): string;
46
+ /**
47
+ * Create a fixture with custom data
48
+ */
49
+ static custom(baseData: Record<string, any>, count?: number): any | any[];
50
+ }
51
+ /**
52
+ * Builder pattern for creating complex fixtures
53
+ */
54
+ export declare class FixtureBuilder<T> {
55
+ private data;
56
+ constructor(initial: T);
57
+ set<K extends keyof T>(key: K, value: T[K]): this;
58
+ merge(overrides: Partial<T>): this;
59
+ build(): T;
60
+ buildMany(count: number): T[];
61
+ }
62
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,uBAAuB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AA0BD,qBAAa,QAAQ;IACjB;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAajD;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAMvE;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAcpD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAM1E;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAalD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,GAAE,MAAU,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE;IAMxE;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,MAAM,GAAE,MAAsB,GAAG,MAAM;IAKpD;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM;IAIhD;;OAEG;IACH,MAAM,CAAC,IAAI,IAAI,MAAM;IAQrB;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,GAAE,MAAU,GAAG,GAAG,GAAG,GAAG,EAAE;CAS/E;AAED;;GAEG;AACH,qBAAa,cAAc,CAAC,CAAC;IACzB,OAAO,CAAC,IAAI,CAAI;gBAEJ,OAAO,EAAE,CAAC;IAItB,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAKjD,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAKlC,KAAK,IAAI,CAAC;IAIV,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE;CAGhC"}
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * Test Fixtures & Data Generators
4
+ * Utilities for creating realistic test data
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.FixtureBuilder = exports.Fixtures = void 0;
8
+ /**
9
+ * Simple seeded random number generator for reproducible tests
10
+ */
11
+ class SeededRandom {
12
+ constructor(seed = Date.now()) {
13
+ this.seed = seed;
14
+ }
15
+ next() {
16
+ this.seed = (this.seed * 9301 + 49297) % 233280;
17
+ return this.seed / 233280;
18
+ }
19
+ nextInt(min, max) {
20
+ return Math.floor(this.next() * (max - min + 1)) + min;
21
+ }
22
+ choice(arr) {
23
+ return arr[Math.floor(this.next() * arr.length)];
24
+ }
25
+ }
26
+ class Fixtures {
27
+ /**
28
+ * Generate a user fixture
29
+ */
30
+ static user(overrides) {
31
+ const id = Math.floor(Math.random() * 100000);
32
+ return {
33
+ id,
34
+ name: `User ${id}`,
35
+ email: `user${id}@example.com`,
36
+ role: 'user',
37
+ createdAt: new Date().toISOString(),
38
+ isActive: true,
39
+ ...overrides
40
+ };
41
+ }
42
+ /**
43
+ * Generate multiple user fixtures
44
+ */
45
+ static users(count = 5, overrides) {
46
+ return Array.from({ length: count }, (_, i) => Fixtures.user({ id: i + 1, ...overrides }));
47
+ }
48
+ /**
49
+ * Generate a product fixture
50
+ */
51
+ static product(overrides) {
52
+ const id = Math.floor(Math.random() * 100000);
53
+ return {
54
+ id,
55
+ name: `Product ${id}`,
56
+ description: 'Test product description',
57
+ price: Math.floor(Math.random() * 10000) / 100,
58
+ stock: Math.floor(Math.random() * 1000),
59
+ sku: `SKU-${id}`,
60
+ createdAt: new Date().toISOString(),
61
+ ...overrides
62
+ };
63
+ }
64
+ /**
65
+ * Generate multiple product fixtures
66
+ */
67
+ static products(count = 5, overrides) {
68
+ return Array.from({ length: count }, (_, i) => Fixtures.product({ id: i + 1, ...overrides }));
69
+ }
70
+ /**
71
+ * Generate an order fixture
72
+ */
73
+ static order(overrides) {
74
+ const id = Math.floor(Math.random() * 100000);
75
+ return {
76
+ id,
77
+ userId: Math.floor(Math.random() * 1000),
78
+ items: [{ productId: 1, quantity: 2, price: 29.99 }],
79
+ total: 59.98,
80
+ status: 'pending',
81
+ createdAt: new Date().toISOString(),
82
+ ...overrides
83
+ };
84
+ }
85
+ /**
86
+ * Generate multiple order fixtures
87
+ */
88
+ static orders(count = 5, overrides) {
89
+ return Array.from({ length: count }, (_, i) => Fixtures.order({ id: i + 1, ...overrides }));
90
+ }
91
+ /**
92
+ * Generate a random email
93
+ */
94
+ static email(domain = 'example.com') {
95
+ const random = Math.random().toString(36).substring(2, 10);
96
+ return `test.${random}@${domain}`;
97
+ }
98
+ /**
99
+ * Generate a random string
100
+ */
101
+ static randomString(length = 10) {
102
+ return Math.random().toString(36).substring(2, 2 + length);
103
+ }
104
+ /**
105
+ * Generate a random UUID
106
+ */
107
+ static uuid() {
108
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
109
+ const r = (Math.random() * 16) | 0;
110
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
111
+ return v.toString(16);
112
+ });
113
+ }
114
+ /**
115
+ * Create a fixture with custom data
116
+ */
117
+ static custom(baseData, count = 1) {
118
+ if (count === 1) {
119
+ return baseData;
120
+ }
121
+ return Array.from({ length: count }, (_, i) => ({
122
+ ...baseData,
123
+ id: i + 1
124
+ }));
125
+ }
126
+ }
127
+ exports.Fixtures = Fixtures;
128
+ /**
129
+ * Builder pattern for creating complex fixtures
130
+ */
131
+ class FixtureBuilder {
132
+ constructor(initial) {
133
+ this.data = JSON.parse(JSON.stringify(initial));
134
+ }
135
+ set(key, value) {
136
+ this.data[key] = value;
137
+ return this;
138
+ }
139
+ merge(overrides) {
140
+ this.data = { ...this.data, ...overrides };
141
+ return this;
142
+ }
143
+ build() {
144
+ return JSON.parse(JSON.stringify(this.data));
145
+ }
146
+ buildMany(count) {
147
+ return Array.from({ length: count }, () => this.build());
148
+ }
149
+ }
150
+ exports.FixtureBuilder = FixtureBuilder;
151
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../src/fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * HTTP Test Utilities
3
+ * Helpers for testing HTTP endpoints and mocking responses
4
+ */
5
+ export interface HttpResponse {
6
+ status: number;
7
+ body: any;
8
+ headers?: Record<string, string>;
9
+ }
10
+ export declare class HttpTestBuilder {
11
+ /**
12
+ * Create a successful response (200)
13
+ */
14
+ static ok(body?: any): HttpResponse;
15
+ /**
16
+ * created response (201)
17
+ */
18
+ static created(body: any): HttpResponse;
19
+ /**
20
+ *bad request response (400)
21
+ */
22
+ static badRequest(message?: string): HttpResponse;
23
+ /**
24
+ * unauthorized response (401)
25
+ */
26
+ static unauthorized(message?: string): HttpResponse;
27
+ /**
28
+ * forbidden response (403)
29
+ */
30
+ static forbidden(message?: string): HttpResponse;
31
+ /**
32
+ * not found response (404)
33
+ */
34
+ static notFound(message?: string): HttpResponse;
35
+ /**
36
+ * server error response (500)
37
+ */
38
+ static serverError(message?: string): HttpResponse;
39
+ /**
40
+ * custom response
41
+ */
42
+ static custom(status: number, body?: any, headers?: Record<string, string>): HttpResponse;
43
+ }
44
+ export declare class HttpResponseAssertion {
45
+ /**
46
+ * response status code
47
+ */
48
+ static assertStatus(response: HttpResponse, expectedStatus: number): void;
49
+ /**
50
+ * response body contains key
51
+ */
52
+ static assertBodyHasKey(response: HttpResponse, key: string): void;
53
+ /**
54
+ * response body matches object
55
+ */
56
+ static assertBodyMatches(response: HttpResponse, expected: Record<string, any>): void;
57
+ /**
58
+ * response header exists
59
+ */
60
+ static assertHeaderExists(response: HttpResponse, headerName: string): void;
61
+ /**
62
+ * response is successful (2xx)
63
+ */
64
+ static assertSuccess(response: HttpResponse): void;
65
+ /**
66
+ * response is error (4xx or 5xx)
67
+ */
68
+ static assertError(response: HttpResponse): void;
69
+ }
70
+ //# sourceMappingURL=http-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-test.d.ts","sourceRoot":"","sources":["../src/http-test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,GAAG,CAAC;IACV,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,qBAAa,eAAe;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,GAAE,GAAQ,GAAG,YAAY;IAQvC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY;IAQvC;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,GAAE,MAAsB,GAAG,YAAY;IAQhE;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,GAAE,MAAuB,GAAG,YAAY;IAQnE;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,MAAoB,GAAG,YAAY;IAQ7D;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,MAAoB,GAAG,YAAY;IAQ5D;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,OAAO,GAAE,MAAgC,GAAG,YAAY;IAQ3E;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,GAAQ,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,YAAY;CAGhG;AAED,qBAAa,qBAAqB;IAC9B;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAQzE;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAQlE;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI;IAYrF;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAS3E;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAQlD;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;CAOnD"}
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP Test Utilities
4
+ * Helpers for testing HTTP endpoints and mocking responses
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.HttpResponseAssertion = exports.HttpTestBuilder = void 0;
8
+ class HttpTestBuilder {
9
+ /**
10
+ * Create a successful response (200)
11
+ */
12
+ static ok(body = {}) {
13
+ return {
14
+ status: 200,
15
+ body,
16
+ headers: { 'Content-Type': 'application/json' }
17
+ };
18
+ }
19
+ /**
20
+ * created response (201)
21
+ */
22
+ static created(body) {
23
+ return {
24
+ status: 201,
25
+ body,
26
+ headers: { 'Content-Type': 'application/json' }
27
+ };
28
+ }
29
+ /**
30
+ *bad request response (400)
31
+ */
32
+ static badRequest(message = 'Bad Request') {
33
+ return {
34
+ status: 400,
35
+ body: { error: message },
36
+ headers: { 'Content-Type': 'application/json' }
37
+ };
38
+ }
39
+ /**
40
+ * unauthorized response (401)
41
+ */
42
+ static unauthorized(message = 'Unauthorized') {
43
+ return {
44
+ status: 401,
45
+ body: { error: message },
46
+ headers: { 'Content-Type': 'application/json' }
47
+ };
48
+ }
49
+ /**
50
+ * forbidden response (403)
51
+ */
52
+ static forbidden(message = 'Forbidden') {
53
+ return {
54
+ status: 403,
55
+ body: { error: message },
56
+ headers: { 'Content-Type': 'application/json' }
57
+ };
58
+ }
59
+ /**
60
+ * not found response (404)
61
+ */
62
+ static notFound(message = 'Not Found') {
63
+ return {
64
+ status: 404,
65
+ body: { error: message },
66
+ headers: { 'Content-Type': 'application/json' }
67
+ };
68
+ }
69
+ /**
70
+ * server error response (500)
71
+ */
72
+ static serverError(message = 'Internal Server Error') {
73
+ return {
74
+ status: 500,
75
+ body: { error: message },
76
+ headers: { 'Content-Type': 'application/json' }
77
+ };
78
+ }
79
+ /**
80
+ * custom response
81
+ */
82
+ static custom(status, body = {}, headers) {
83
+ return { status, body, headers };
84
+ }
85
+ }
86
+ exports.HttpTestBuilder = HttpTestBuilder;
87
+ class HttpResponseAssertion {
88
+ /**
89
+ * response status code
90
+ */
91
+ static assertStatus(response, expectedStatus) {
92
+ if (response.status !== expectedStatus) {
93
+ throw new Error(`Expected status ${expectedStatus}, but got ${response.status}`);
94
+ }
95
+ }
96
+ /**
97
+ * response body contains key
98
+ */
99
+ static assertBodyHasKey(response, key) {
100
+ if (!(key in response.body)) {
101
+ throw new Error(`Expected response body to contain key "${key}", but it doesn't`);
102
+ }
103
+ }
104
+ /**
105
+ * response body matches object
106
+ */
107
+ static assertBodyMatches(response, expected) {
108
+ for (const [key, value] of Object.entries(expected)) {
109
+ if (response.body[key] !== value) {
110
+ throw new Error(`Expected body.${key} to be ${JSON.stringify(value)}, but got ${JSON.stringify(response.body[key])}`);
111
+ }
112
+ }
113
+ }
114
+ /**
115
+ * response header exists
116
+ */
117
+ static assertHeaderExists(response, headerName) {
118
+ const headers = response.headers || {};
119
+ if (!(headerName in headers)) {
120
+ throw new Error(`Expected header "${headerName}" to exist, but it doesn't`);
121
+ }
122
+ }
123
+ /**
124
+ * response is successful (2xx)
125
+ */
126
+ static assertSuccess(response) {
127
+ if (response.status < 200 || response.status >= 300) {
128
+ throw new Error(`Expected successful response (2xx), but got ${response.status}`);
129
+ }
130
+ }
131
+ /**
132
+ * response is error (4xx or 5xx)
133
+ */
134
+ static assertError(response) {
135
+ if (response.status < 400) {
136
+ throw new Error(`Expected error response (4xx/5xx), but got ${response.status}`);
137
+ }
138
+ }
139
+ }
140
+ exports.HttpResponseAssertion = HttpResponseAssertion;
141
+ //# sourceMappingURL=http-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-test.js","sourceRoot":"","sources":["../src/http-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @backend-master/test-utils
3
+ * Comprehensive testing utilities for Node.js backend projects
4
+ */
5
+ import { MockDatabase } from './mock-db';
6
+ import { HttpTestBuilder, HttpResponseAssertion } from './http-test';
7
+ import { Fixtures, FixtureBuilder } from './fixtures';
8
+ import { Spy, MockObject, Defer } from './spy';
9
+ export { MockDatabase, MockDBConfig } from './mock-db';
10
+ export { HttpTestBuilder, HttpResponseAssertion, HttpResponse } from './http-test';
11
+ export { Fixtures, FixtureBuilder } from './fixtures';
12
+ export { Spy, MockObject, Defer, CallInfo } from './spy';
13
+ export declare const testUtils: {
14
+ db: {
15
+ MockDatabase: typeof MockDatabase;
16
+ };
17
+ http: {
18
+ HttpTestBuilder: typeof HttpTestBuilder;
19
+ HttpResponseAssertion: typeof HttpResponseAssertion;
20
+ };
21
+ fixtures: {
22
+ Fixtures: typeof Fixtures;
23
+ FixtureBuilder: typeof FixtureBuilder;
24
+ };
25
+ mocks: {
26
+ Spy: typeof Spy;
27
+ MockObject: typeof MockObject;
28
+ Defer: typeof Defer;
29
+ };
30
+ };
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGzD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;CAiBrB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * @backend-master/test-utils
4
+ * Comprehensive testing utilities for Node.js backend projects
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.testUtils = exports.Defer = exports.MockObject = exports.Spy = exports.FixtureBuilder = exports.Fixtures = exports.HttpResponseAssertion = exports.HttpTestBuilder = exports.MockDatabase = void 0;
8
+ const mock_db_1 = require("./mock-db");
9
+ const http_test_1 = require("./http-test");
10
+ const fixtures_1 = require("./fixtures");
11
+ const spy_1 = require("./spy");
12
+ var mock_db_2 = require("./mock-db");
13
+ Object.defineProperty(exports, "MockDatabase", { enumerable: true, get: function () { return mock_db_2.MockDatabase; } });
14
+ var http_test_2 = require("./http-test");
15
+ Object.defineProperty(exports, "HttpTestBuilder", { enumerable: true, get: function () { return http_test_2.HttpTestBuilder; } });
16
+ Object.defineProperty(exports, "HttpResponseAssertion", { enumerable: true, get: function () { return http_test_2.HttpResponseAssertion; } });
17
+ var fixtures_2 = require("./fixtures");
18
+ Object.defineProperty(exports, "Fixtures", { enumerable: true, get: function () { return fixtures_2.Fixtures; } });
19
+ Object.defineProperty(exports, "FixtureBuilder", { enumerable: true, get: function () { return fixtures_2.FixtureBuilder; } });
20
+ var spy_2 = require("./spy");
21
+ Object.defineProperty(exports, "Spy", { enumerable: true, get: function () { return spy_2.Spy; } });
22
+ Object.defineProperty(exports, "MockObject", { enumerable: true, get: function () { return spy_2.MockObject; } });
23
+ Object.defineProperty(exports, "Defer", { enumerable: true, get: function () { return spy_2.Defer; } });
24
+ // Convenience exports
25
+ exports.testUtils = {
26
+ db: {
27
+ MockDatabase: mock_db_1.MockDatabase
28
+ },
29
+ http: {
30
+ HttpTestBuilder: http_test_1.HttpTestBuilder,
31
+ HttpResponseAssertion: http_test_1.HttpResponseAssertion
32
+ },
33
+ fixtures: {
34
+ Fixtures: fixtures_1.Fixtures,
35
+ FixtureBuilder: fixtures_1.FixtureBuilder
36
+ },
37
+ mocks: {
38
+ Spy: spy_1.Spy,
39
+ MockObject: spy_1.MockObject,
40
+ Defer: spy_1.Defer
41
+ }
42
+ };
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,uCAAyC;AACzC,2CAAqE;AACrE,yCAAsD;AACtD,+BAA+C;AAE/C,qCAAuD;AAA9C,uGAAA,YAAY,OAAA;AACrB,yCAAmF;AAA1E,4GAAA,eAAe,OAAA;AAAE,kHAAA,qBAAqB,OAAA;AAC/C,uCAAsD;AAA7C,oGAAA,QAAQ,OAAA;AAAE,0GAAA,cAAc,OAAA;AACjC,6BAAyD;AAAhD,0FAAA,GAAG,OAAA;AAAE,iGAAA,UAAU,OAAA;AAAE,4FAAA,KAAK,OAAA;AAE/B,sBAAsB;AACT,QAAA,SAAS,GAAG;IACrB,EAAE,EAAE;QACA,YAAY,EAAZ,sBAAY;KACf;IACD,IAAI,EAAE;QACF,eAAe,EAAf,2BAAe;QACf,qBAAqB,EAArB,iCAAqB;KACxB;IACD,QAAQ,EAAE;QACN,QAAQ,EAAR,mBAAQ;QACR,cAAc,EAAd,yBAAc;KACjB;IACD,KAAK,EAAE;QACH,GAAG,EAAH,SAAG;QACH,UAAU,EAAV,gBAAU;QACV,KAAK,EAAL,WAAK;KACR;CACJ,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Mock Database Helper
3
+ * Provides in-memory database simulation for testing without external dependencies
4
+ */
5
+ export interface MockDBConfig {
6
+ tables?: Record<string, any[]>;
7
+ autoIncrement?: boolean;
8
+ }
9
+ export declare class MockDatabase {
10
+ private tables;
11
+ private autoIncrement;
12
+ private idCounters;
13
+ constructor(config?: MockDBConfig);
14
+ /**
15
+ * Create a new table
16
+ */
17
+ createTable(name: string, schema?: Record<string, string>): void;
18
+ /**
19
+ * Insert a record into a table
20
+ */
21
+ insert(table: string, record: any): any;
22
+ /**
23
+ * Find records matching a query
24
+ */
25
+ find(table: string, query?: Record<string, any>): any[];
26
+ /**
27
+ * Find a single record
28
+ */
29
+ findOne(table: string, query: Record<string, any>): any | null;
30
+ /**
31
+ * Update records matching a query
32
+ */
33
+ update(table: string, query: Record<string, any>, updates: Record<string, any>): number;
34
+ /**
35
+ * Delete records matching a query
36
+ */
37
+ delete(table: string, query: Record<string, any>): number;
38
+ /**
39
+ * Get all records from a table
40
+ */
41
+ getAll(table: string): any[];
42
+ /**
43
+ * Clear a table
44
+ */
45
+ clearTable(table: string): void;
46
+ /**
47
+ * Clear all tables
48
+ */
49
+ clearAll(): void;
50
+ /**
51
+ * Get table count
52
+ */
53
+ count(table: string): number;
54
+ }
55
+ //# sourceMappingURL=mock-db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-db.d.ts","sourceRoot":"","sources":["../src/mock-db.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,UAAU,CAAkC;gBAExC,MAAM,CAAC,EAAE,YAAY;IASjC;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAQhE;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,GAAG;IAgBvC;;OAEG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAAG,GAAG,EAAE;IAW3D;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,IAAI;IAK9D;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAqBvF;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;IAoBzD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAO5B;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ/B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAKhB;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAM/B"}
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Mock Database Helper
4
+ * Provides in-memory database simulation for testing without external dependencies
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.MockDatabase = void 0;
8
+ class MockDatabase {
9
+ constructor(config) {
10
+ this.tables = new Map();
11
+ this.idCounters = new Map();
12
+ this.autoIncrement = config?.autoIncrement ?? true;
13
+ if (config?.tables) {
14
+ Object.entries(config.tables).forEach(([table, data]) => {
15
+ this.tables.set(table, JSON.parse(JSON.stringify(data)));
16
+ });
17
+ }
18
+ }
19
+ /**
20
+ * Create a new table
21
+ */
22
+ createTable(name, schema) {
23
+ if (this.tables.has(name)) {
24
+ throw new Error(`Table "${name}" already exists`);
25
+ }
26
+ this.tables.set(name, []);
27
+ this.idCounters.set(name, 0);
28
+ }
29
+ /**
30
+ * Insert a record into a table
31
+ */
32
+ insert(table, record) {
33
+ if (!this.tables.has(table)) {
34
+ throw new Error(`Table "${table}" does not exist`);
35
+ }
36
+ const newRecord = { ...record };
37
+ if (this.autoIncrement && !newRecord.id) {
38
+ const count = this.idCounters.get(table) || 0;
39
+ newRecord.id = count + 1;
40
+ this.idCounters.set(table, count + 1);
41
+ }
42
+ this.tables.get(table).push(newRecord);
43
+ return newRecord;
44
+ }
45
+ /**
46
+ * Find records matching a query
47
+ */
48
+ find(table, query = {}) {
49
+ if (!this.tables.has(table)) {
50
+ throw new Error(`Table "${table}" does not exist`);
51
+ }
52
+ const records = this.tables.get(table);
53
+ return records.filter(record => Object.entries(query).every(([key, value]) => record[key] === value));
54
+ }
55
+ /**
56
+ * Find a single record
57
+ */
58
+ findOne(table, query) {
59
+ const results = this.find(table, query);
60
+ return results.length > 0 ? results[0] : null;
61
+ }
62
+ /**
63
+ * Update records matching a query
64
+ */
65
+ update(table, query, updates) {
66
+ if (!this.tables.has(table)) {
67
+ throw new Error(`Table "${table}" does not exist`);
68
+ }
69
+ const records = this.tables.get(table);
70
+ let updated = 0;
71
+ for (let i = 0; i < records.length; i++) {
72
+ const matches = Object.entries(query).every(([key, value]) => records[i][key] === value);
73
+ if (matches) {
74
+ records[i] = { ...records[i], ...updates };
75
+ updated++;
76
+ }
77
+ }
78
+ return updated;
79
+ }
80
+ /**
81
+ * Delete records matching a query
82
+ */
83
+ delete(table, query) {
84
+ if (!this.tables.has(table)) {
85
+ throw new Error(`Table "${table}" does not exist`);
86
+ }
87
+ const records = this.tables.get(table);
88
+ const originalLength = records.length;
89
+ for (let i = records.length - 1; i >= 0; i--) {
90
+ const matches = Object.entries(query).every(([key, value]) => records[i][key] === value);
91
+ if (matches) {
92
+ records.splice(i, 1);
93
+ }
94
+ }
95
+ return originalLength - records.length;
96
+ }
97
+ /**
98
+ * Get all records from a table
99
+ */
100
+ getAll(table) {
101
+ if (!this.tables.has(table)) {
102
+ throw new Error(`Table "${table}" does not exist`);
103
+ }
104
+ return JSON.parse(JSON.stringify(this.tables.get(table)));
105
+ }
106
+ /**
107
+ * Clear a table
108
+ */
109
+ clearTable(table) {
110
+ if (!this.tables.has(table)) {
111
+ throw new Error(`Table "${table}" does not exist`);
112
+ }
113
+ this.tables.set(table, []);
114
+ this.idCounters.set(table, 0);
115
+ }
116
+ /**
117
+ * Clear all tables
118
+ */
119
+ clearAll() {
120
+ this.tables.clear();
121
+ this.idCounters.clear();
122
+ }
123
+ /**
124
+ * Get table count
125
+ */
126
+ count(table) {
127
+ if (!this.tables.has(table)) {
128
+ throw new Error(`Table "${table}" does not exist`);
129
+ }
130
+ return this.tables.get(table).length;
131
+ }
132
+ }
133
+ exports.MockDatabase = MockDatabase;
134
+ //# sourceMappingURL=mock-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-db.js","sourceRoot":"","sources":["../src/mock-db.ts"],"names":[],"mappings":"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"}
package/dist/spy.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Spy & Mock Utilities
3
+ * Helpers for tracking function calls and creating mocks
4
+ */
5
+ export interface CallInfo {
6
+ args: any[];
7
+ returnValue?: any;
8
+ error?: Error;
9
+ timestamp: number;
10
+ }
11
+ export declare class Spy {
12
+ private calls;
13
+ private _returnValue;
14
+ private shouldThrow;
15
+ /**
16
+ * Create a spy function
17
+ */
18
+ static create<T extends (...args: any[]) => any>(implementation?: T): T & Spy;
19
+ /**
20
+ * Set return value
21
+ */
22
+ returnValue(value: any): this;
23
+ /**
24
+ * Set up to throw error
25
+ */
26
+ throwError(error: Error | string): this;
27
+ /**
28
+ * Implement with custom function
29
+ */
30
+ implementWith<T extends (...args: any[]) => any>(fn: T): T & Spy;
31
+ /**
32
+ * Record a call
33
+ */
34
+ private record;
35
+ /**
36
+ * Get all calls
37
+ */
38
+ getCalls(): CallInfo[];
39
+ /**
40
+ * Get call count
41
+ */
42
+ getCallCount(): number;
43
+ /**
44
+ * Get arguments of nth call
45
+ */
46
+ getArguments(callIndex?: number): any[];
47
+ /**
48
+ * Get first argument of first call
49
+ */
50
+ getFirstArg(): any;
51
+ /**
52
+ * Check if called
53
+ */
54
+ wasCalled(): boolean;
55
+ /**
56
+ * Check if called with specific arguments
57
+ */
58
+ wasCalledWith(...args: any[]): boolean;
59
+ /**
60
+ * Reset spy
61
+ */
62
+ reset(): void;
63
+ }
64
+ /**
65
+ * Maps an object type so function-valued properties become T[K] & Spy
66
+ */
67
+ type SpyWrapped<T> = {
68
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] & Spy : T[K];
69
+ };
70
+ /**
71
+ * Maps an object type so all properties become T[K] & Spy
72
+ */
73
+ type AllSpies<T> = {
74
+ [K in keyof T]: T[K] & Spy;
75
+ };
76
+ /**
77
+ * Simple object mocking
78
+ */
79
+ export declare class MockObject {
80
+ static create<T extends Record<string, any>>(shape: Partial<T>): SpyWrapped<T>;
81
+ /**
82
+ * Create a mock with all methods as spies
83
+ */
84
+ static createWithSpies<T extends Record<string, any>>(keys: (keyof T)[]): AllSpies<T>;
85
+ }
86
+ /**
87
+ * Defer helper for testing async code
88
+ */
89
+ export declare class Defer<T> {
90
+ promise: Promise<T>;
91
+ resolve: (value: T) => void;
92
+ reject: (reason?: any) => void;
93
+ constructor();
94
+ }
95
+ export {};
96
+ //# sourceMappingURL=spy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spy.d.ts","sourceRoot":"","sources":["../src/spy.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,GAAG;IACZ,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAsB;IAEzC;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC3C,cAAc,CAAC,EAAE,CAAC,GACnB,CAAC,GAAG,GAAG;IAwBV;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI;IAKvC;;OAEG;IACH,aAAa,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,GAAG;IAmChE;;OAEG;IACH,OAAO,CAAC,MAAM;IAiBd;;OAEG;IACH,QAAQ,IAAI,QAAQ,EAAE;IAItB;;OAEG;IACH,YAAY,IAAI,MAAM;IAItB;;OAEG;IACH,YAAY,CAAC,SAAS,GAAE,MAAU,GAAG,GAAG,EAAE;IAO1C;;OAEG;IACH,WAAW,IAAI,GAAG;IAIlB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,aAAa,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO;IAMtC;;OAEG;IACH,KAAK,IAAI,IAAI;CAKhB;AAED;;GAEG;AACH,KAAK,UAAU,CAAC,CAAC,IAAI;KAChB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;CAC3E,CAAC;AAEF;;GAEG;AACH,KAAK,QAAQ,CAAC,CAAC,IAAI;KACd,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;CAC7B,CAAC;AAEF;;GAEG;AACH,qBAAa,UAAU;IACnB,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAc9E;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChD,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,GAClB,QAAQ,CAAC,CAAC,CAAC;CASjB;AAGD;;GAEG;AACH,qBAAa,KAAK,CAAC,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,EAAG,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,MAAM,EAAG,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;;CAQnC"}
package/dist/spy.js ADDED
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /**
3
+ * Spy & Mock Utilities
4
+ * Helpers for tracking function calls and creating mocks
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Defer = exports.MockObject = exports.Spy = void 0;
8
+ class Spy {
9
+ constructor() {
10
+ this.calls = [];
11
+ this.shouldThrow = null;
12
+ }
13
+ /**
14
+ * Create a spy function
15
+ */
16
+ static create(implementation) {
17
+ const spy = new Spy();
18
+ if (implementation) {
19
+ return spy.implementWith(implementation);
20
+ }
21
+ const fn = function (...args) {
22
+ return spy.record(args);
23
+ };
24
+ Object.setPrototypeOf(fn, Spy.prototype);
25
+ // Proxy property access to the spy instance so state stays in sync
26
+ for (const prop of ['calls', '_returnValue', 'shouldThrow']) {
27
+ Object.defineProperty(fn, prop, {
28
+ get: () => spy[prop],
29
+ set: (v) => { spy[prop] = v; },
30
+ configurable: true
31
+ });
32
+ }
33
+ return fn;
34
+ }
35
+ /**
36
+ * Set return value
37
+ */
38
+ returnValue(value) {
39
+ this._returnValue = value;
40
+ this.shouldThrow = null;
41
+ return this;
42
+ }
43
+ /**
44
+ * Set up to throw error
45
+ */
46
+ throwError(error) {
47
+ this.shouldThrow = typeof error === 'string' ? new Error(error) : error;
48
+ return this;
49
+ }
50
+ /**
51
+ * Implement with custom function
52
+ */
53
+ implementWith(fn) {
54
+ const spy = this;
55
+ const originalFn = fn;
56
+ const wrappedFn = function (...args) {
57
+ try {
58
+ const result = originalFn(...args);
59
+ spy.calls.push({
60
+ args,
61
+ returnValue: result,
62
+ timestamp: Date.now()
63
+ });
64
+ return result;
65
+ }
66
+ catch (error) {
67
+ spy.calls.push({
68
+ args,
69
+ error: error,
70
+ timestamp: Date.now()
71
+ });
72
+ throw error;
73
+ }
74
+ };
75
+ Object.setPrototypeOf(wrappedFn, Spy.prototype);
76
+ // Proxy property access to the spy instance so state stays in sync
77
+ for (const prop of ['calls', '_returnValue', 'shouldThrow']) {
78
+ Object.defineProperty(wrappedFn, prop, {
79
+ get: () => spy[prop],
80
+ set: (v) => { spy[prop] = v; },
81
+ configurable: true
82
+ });
83
+ }
84
+ return wrappedFn;
85
+ }
86
+ /**
87
+ * Record a call
88
+ */
89
+ record(args) {
90
+ const callInfo = {
91
+ args,
92
+ timestamp: Date.now()
93
+ };
94
+ if (this.shouldThrow) {
95
+ callInfo.error = this.shouldThrow;
96
+ this.calls.push(callInfo);
97
+ throw this.shouldThrow;
98
+ }
99
+ callInfo.returnValue = this._returnValue;
100
+ this.calls.push(callInfo);
101
+ return this._returnValue;
102
+ }
103
+ /**
104
+ * Get all calls
105
+ */
106
+ getCalls() {
107
+ return [...this.calls];
108
+ }
109
+ /**
110
+ * Get call count
111
+ */
112
+ getCallCount() {
113
+ return this.calls.length;
114
+ }
115
+ /**
116
+ * Get arguments of nth call
117
+ */
118
+ getArguments(callIndex = 0) {
119
+ if (callIndex >= this.calls.length) {
120
+ throw new Error(`Call at index ${callIndex} does not exist`);
121
+ }
122
+ return this.calls[callIndex].args;
123
+ }
124
+ /**
125
+ * Get first argument of first call
126
+ */
127
+ getFirstArg() {
128
+ return this.getArguments(0)[0];
129
+ }
130
+ /**
131
+ * Check if called
132
+ */
133
+ wasCalled() {
134
+ return this.calls.length > 0;
135
+ }
136
+ /**
137
+ * Check if called with specific arguments
138
+ */
139
+ wasCalledWith(...args) {
140
+ return this.calls.some(call => JSON.stringify(call.args) === JSON.stringify(args));
141
+ }
142
+ /**
143
+ * Reset spy
144
+ */
145
+ reset() {
146
+ this.calls.length = 0;
147
+ this._returnValue = undefined;
148
+ this.shouldThrow = null;
149
+ }
150
+ }
151
+ exports.Spy = Spy;
152
+ /**
153
+ * Simple object mocking
154
+ */
155
+ class MockObject {
156
+ static create(shape) {
157
+ const mock = {};
158
+ for (const [key, value] of Object.entries(shape)) {
159
+ if (typeof value === 'function') {
160
+ mock[key] = Spy.create(value);
161
+ }
162
+ else {
163
+ mock[key] = value;
164
+ }
165
+ }
166
+ return mock;
167
+ }
168
+ /**
169
+ * Create a mock with all methods as spies
170
+ */
171
+ static createWithSpies(keys) {
172
+ const mock = {};
173
+ for (const key of keys) {
174
+ mock[key] = Spy.create();
175
+ }
176
+ return mock;
177
+ }
178
+ }
179
+ exports.MockObject = MockObject;
180
+ /**
181
+ * Defer helper for testing async code
182
+ */
183
+ class Defer {
184
+ constructor() {
185
+ this.promise = new Promise((resolve, reject) => {
186
+ this.resolve = resolve;
187
+ this.reject = reject;
188
+ });
189
+ }
190
+ }
191
+ exports.Defer = Defer;
192
+ //# sourceMappingURL=spy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spy.js","sourceRoot":"","sources":["../src/spy.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@backend-master/test-utils",
3
+ "version": "1.0.0",
4
+ "description": "Comprehensive testing utilities for Node.js backend projects",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "test": "jest",
14
+ "test:watch": "jest --watch",
15
+ "test:coverage": "jest --coverage",
16
+ "lint": "eslint src/**/*.ts",
17
+ "prepare": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "testing",
21
+ "node.js",
22
+ "backend",
23
+ "utilities",
24
+ "mock",
25
+ "fixture",
26
+ "assertion"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": ""
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "@types/jest": "^29.5.0",
39
+ "@types/node": "^20.0.0",
40
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
41
+ "@typescript-eslint/parser": "^6.0.0",
42
+ "eslint": "^8.0.0",
43
+ "jest": "^29.5.0",
44
+ "ts-jest": "^29.1.0",
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "jest": ">=25.0.0"
49
+ },
50
+ "peerDependenciesOptional": true
51
+ }