te.js 2.0.1 → 2.0.3
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/package.json +13 -2
- package/.cursor/plans/ai_native_framework_features_5bb1a20a.plan.md +0 -234
- package/.cursor/plans/auto_error_fix_agent_e68979c5.plan.md +0 -356
- package/.cursor/plans/tejas_framework_test_suite_5e3c6fad.plan.md +0 -168
- package/.prettierignore +0 -31
- package/.prettierrc +0 -5
- package/example/API_OVERVIEW.md +0 -77
- package/example/README.md +0 -155
- package/example/index.js +0 -29
- package/example/middlewares/auth.js +0 -9
- package/example/middlewares/global.midair.js +0 -6
- package/example/openapi.json +0 -390
- package/example/package.json +0 -23
- package/example/services/cache.service.js +0 -25
- package/example/services/user.service.js +0 -42
- package/example/start-redis.js +0 -2
- package/example/targets/cache.target.js +0 -35
- package/example/targets/index.target.js +0 -16
- package/example/targets/users.target.js +0 -60
- package/example/tejas.config.json +0 -22
- package/tests/auto-docs/handler-analyzer.test.js +0 -44
- package/tests/auto-docs/openapi-generator.test.js +0 -103
- package/tests/auto-docs/parse.test.js +0 -63
- package/tests/auto-docs/source-resolver.test.js +0 -58
- package/tests/helpers/index.js +0 -37
- package/tests/helpers/mock-http.js +0 -342
- package/tests/helpers/test-utils.js +0 -446
- package/tests/setup.test.js +0 -148
- package/vitest.config.js +0 -54
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for auto-docs openapi/generator pure functions.
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
toOpenAPIPath,
|
|
7
|
-
getPathParameters,
|
|
8
|
-
getQueryParameters,
|
|
9
|
-
buildSchemaFromMetadata,
|
|
10
|
-
buildResponses,
|
|
11
|
-
buildOperation,
|
|
12
|
-
mergeMetadata,
|
|
13
|
-
} from '../../auto-docs/openapi/generator.js';
|
|
14
|
-
|
|
15
|
-
describe('openapi-generator', () => {
|
|
16
|
-
describe('toOpenAPIPath', () => {
|
|
17
|
-
it('converts :param to {param}', () => {
|
|
18
|
-
expect(toOpenAPIPath('/users/:id')).toBe('/users/{id}');
|
|
19
|
-
});
|
|
20
|
-
it('converts multiple params', () => {
|
|
21
|
-
expect(toOpenAPIPath('/users/:userId/posts/:postId')).toBe('/users/{userId}/posts/{postId}');
|
|
22
|
-
});
|
|
23
|
-
it('returns / for empty or non-string', () => {
|
|
24
|
-
expect(toOpenAPIPath('')).toBe('/');
|
|
25
|
-
expect(toOpenAPIPath(null)).toBe('/');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('getPathParameters', () => {
|
|
30
|
-
it('extracts path params from te.js path', () => {
|
|
31
|
-
const params = getPathParameters('/users/:id');
|
|
32
|
-
expect(params).toHaveLength(1);
|
|
33
|
-
expect(params[0]).toMatchObject({ name: 'id', in: 'path', required: true, schema: { type: 'string' } });
|
|
34
|
-
});
|
|
35
|
-
it('returns empty array for path without params', () => {
|
|
36
|
-
expect(getPathParameters('/users')).toEqual([]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('getQueryParameters', () => {
|
|
41
|
-
it('builds query params from metadata', () => {
|
|
42
|
-
const queryMeta = { limit: { type: 'integer', required: false }, q: { type: 'string', required: true } };
|
|
43
|
-
const params = getQueryParameters(queryMeta);
|
|
44
|
-
expect(params).toHaveLength(2);
|
|
45
|
-
expect(params.find((p) => p.name === 'limit')).toMatchObject({ in: 'query', required: false });
|
|
46
|
-
expect(params.find((p) => p.name === 'q')).toMatchObject({ in: 'query', required: true });
|
|
47
|
-
});
|
|
48
|
-
it('returns empty array for invalid meta', () => {
|
|
49
|
-
expect(getQueryParameters(null)).toEqual([]);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('buildSchemaFromMetadata', () => {
|
|
54
|
-
it('builds OpenAPI schema from field meta', () => {
|
|
55
|
-
const meta = { name: { type: 'string' }, email: { type: 'string', format: 'email', required: true } };
|
|
56
|
-
const schema = buildSchemaFromMetadata(meta);
|
|
57
|
-
expect(schema.type).toBe('object');
|
|
58
|
-
expect(schema.properties.name.type).toBe('string');
|
|
59
|
-
expect(schema.properties.email.format).toBe('email');
|
|
60
|
-
expect(schema.required).toContain('email');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('buildResponses', () => {
|
|
65
|
-
it('returns 200 Success when responseMeta empty', () => {
|
|
66
|
-
const r = buildResponses(null);
|
|
67
|
-
expect(r['200']).toEqual({ description: 'Success' });
|
|
68
|
-
});
|
|
69
|
-
it('builds responses from metadata', () => {
|
|
70
|
-
const meta = { 200: { description: 'OK' }, 201: { description: 'Created' } };
|
|
71
|
-
const r = buildResponses(meta);
|
|
72
|
-
expect(r['200'].description).toBe('OK');
|
|
73
|
-
expect(r['201'].description).toBe('Created');
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('buildOperation', () => {
|
|
78
|
-
it('builds operation with summary and responses', () => {
|
|
79
|
-
const meta = { summary: 'Get user', response: { 200: { description: 'OK' } } };
|
|
80
|
-
const pathParams = [];
|
|
81
|
-
const op = buildOperation('get', meta, pathParams);
|
|
82
|
-
expect(op.summary).toBe('Get user');
|
|
83
|
-
expect(op.responses['200'].description).toBe('OK');
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('mergeMetadata', () => {
|
|
88
|
-
it('prefers explicit when preferEnhanced false', () => {
|
|
89
|
-
const explicit = { summary: 'A', description: 'B' };
|
|
90
|
-
const enhanced = { summary: 'C', description: 'D' };
|
|
91
|
-
const merged = mergeMetadata(explicit, enhanced, { preferEnhanced: false });
|
|
92
|
-
expect(merged.summary).toBe('A');
|
|
93
|
-
expect(merged.description).toBe('B');
|
|
94
|
-
});
|
|
95
|
-
it('prefers enhanced when preferEnhanced true', () => {
|
|
96
|
-
const explicit = { summary: 'A' };
|
|
97
|
-
const enhanced = { summary: 'C', description: 'D' };
|
|
98
|
-
const merged = mergeMetadata(explicit, enhanced, { preferEnhanced: true });
|
|
99
|
-
expect(merged.summary).toBe('C');
|
|
100
|
-
expect(merged.description).toBe('D');
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for auto-docs llm/parse (extractJSON, extractJSONArray, reconcileOrderedTags).
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
extractJSON,
|
|
7
|
-
extractJSONArray,
|
|
8
|
-
reconcileOrderedTags,
|
|
9
|
-
} from '../../auto-docs/llm/parse.js';
|
|
10
|
-
|
|
11
|
-
describe('llm/parse', () => {
|
|
12
|
-
describe('extractJSON', () => {
|
|
13
|
-
it('extracts object from plain JSON string', () => {
|
|
14
|
-
const str = '{"name":"Users","description":"CRUD"}';
|
|
15
|
-
expect(extractJSON(str)).toEqual({ name: 'Users', description: 'CRUD' });
|
|
16
|
-
});
|
|
17
|
-
it('extracts first object from text with markdown', () => {
|
|
18
|
-
const str = 'Here is the result:\n```json\n{"summary":"Get item"}\n```';
|
|
19
|
-
expect(extractJSON(str)).toEqual({ summary: 'Get item' });
|
|
20
|
-
});
|
|
21
|
-
it('returns null for empty or no object', () => {
|
|
22
|
-
expect(extractJSON('')).toBeNull();
|
|
23
|
-
expect(extractJSON('no brace here')).toBeNull();
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe('extractJSONArray', () => {
|
|
28
|
-
it('extracts array from string', () => {
|
|
29
|
-
const str = '["Users", "Auth", "Health"]';
|
|
30
|
-
expect(extractJSONArray(str)).toEqual(['Users', 'Auth', 'Health']);
|
|
31
|
-
});
|
|
32
|
-
it('returns null when no array', () => {
|
|
33
|
-
expect(extractJSONArray('')).toBeNull();
|
|
34
|
-
expect(extractJSONArray('nothing')).toBeNull();
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
describe('reconcileOrderedTags', () => {
|
|
39
|
-
it('reorders tags by orderedTagNames', () => {
|
|
40
|
-
const tags = [
|
|
41
|
-
{ name: 'Health', description: '...' },
|
|
42
|
-
{ name: 'Users', description: '...' },
|
|
43
|
-
{ name: 'Auth', description: '...' },
|
|
44
|
-
];
|
|
45
|
-
const ordered = reconcileOrderedTags(['Users', 'Auth', 'Health'], tags);
|
|
46
|
-
expect(ordered.map((t) => t.name)).toEqual(['Users', 'Auth', 'Health']);
|
|
47
|
-
});
|
|
48
|
-
it('appends tags not in orderedTagNames', () => {
|
|
49
|
-
const tags = [
|
|
50
|
-
{ name: 'Users' },
|
|
51
|
-
{ name: 'Other' },
|
|
52
|
-
];
|
|
53
|
-
const ordered = reconcileOrderedTags(['Users'], tags);
|
|
54
|
-
expect(ordered.map((t) => t.name)).toEqual(['Users', 'Other']);
|
|
55
|
-
});
|
|
56
|
-
it('returns copy of tags when orderedTagNames empty', () => {
|
|
57
|
-
const tags = [{ name: 'A' }];
|
|
58
|
-
const ordered = reconcileOrderedTags([], tags);
|
|
59
|
-
expect(ordered).toEqual([{ name: 'A' }]);
|
|
60
|
-
expect(ordered).not.toBe(tags);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for auto-docs source-resolver (extractRelativeImports, resolveTargetFilePath, formatDependencyContext).
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'vitest';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import {
|
|
7
|
-
extractRelativeImports,
|
|
8
|
-
resolveTargetFilePath,
|
|
9
|
-
formatDependencyContext,
|
|
10
|
-
} from '../../auto-docs/analysis/source-resolver.js';
|
|
11
|
-
|
|
12
|
-
describe('source-resolver', () => {
|
|
13
|
-
describe('extractRelativeImports', () => {
|
|
14
|
-
it('extracts relative import paths', () => {
|
|
15
|
-
const source = `import x from './foo.js';\nimport { y } from '../bar.js';`;
|
|
16
|
-
const imports = extractRelativeImports(source);
|
|
17
|
-
expect(imports).toContain('./foo.js');
|
|
18
|
-
expect(imports).toContain('../bar.js');
|
|
19
|
-
});
|
|
20
|
-
it('ignores absolute or node imports', () => {
|
|
21
|
-
const source = `import path from 'path';\nimport x from './local.js';`;
|
|
22
|
-
const imports = extractRelativeImports(source);
|
|
23
|
-
expect(imports).toEqual(['./local.js']);
|
|
24
|
-
});
|
|
25
|
-
it('returns unique paths', () => {
|
|
26
|
-
const source = `import a from './foo.js';\nimport b from './foo.js';`;
|
|
27
|
-
expect(extractRelativeImports(source)).toHaveLength(1);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('resolveTargetFilePath', () => {
|
|
32
|
-
it('joins dirTargets with groupId and .target.js', () => {
|
|
33
|
-
const resolved = resolveTargetFilePath('users', 'targets');
|
|
34
|
-
expect(resolved).toMatch(/targets[\\/]users\.target\.js$/);
|
|
35
|
-
});
|
|
36
|
-
it('converts groupId slashes to path sep', () => {
|
|
37
|
-
const resolved = resolveTargetFilePath('subdir/users', 'targets');
|
|
38
|
-
expect(resolved).toMatch(/subdir[\\/]users\.target\.js$/);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('formatDependencyContext', () => {
|
|
43
|
-
it('returns empty string for empty sources', () => {
|
|
44
|
-
expect(formatDependencyContext(new Map(), null)).toBe('');
|
|
45
|
-
});
|
|
46
|
-
it('formats map of path -> source with labels', () => {
|
|
47
|
-
const sources = new Map([
|
|
48
|
-
['/cwd/targets/users.target.js', 'const x = 1;'],
|
|
49
|
-
['/cwd/services/user.js', 'const y = 2;'],
|
|
50
|
-
]);
|
|
51
|
-
const targetPath = '/cwd/targets/users.target.js';
|
|
52
|
-
const out = formatDependencyContext(sources, targetPath, 1000);
|
|
53
|
-
expect(out).toContain('Target');
|
|
54
|
-
expect(out).toContain('const x = 1;');
|
|
55
|
-
expect(out).toContain('const y = 2;');
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
package/tests/helpers/index.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Helpers Index
|
|
3
|
-
* Central export for all test utilities and mocks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// HTTP Mocks
|
|
7
|
-
export {
|
|
8
|
-
MockRequest,
|
|
9
|
-
MockResponse,
|
|
10
|
-
createMockRequest,
|
|
11
|
-
createMockResponse,
|
|
12
|
-
createMockPair,
|
|
13
|
-
createJsonRequest,
|
|
14
|
-
createFormRequest,
|
|
15
|
-
createMultipartRequest,
|
|
16
|
-
createMultipartBody,
|
|
17
|
-
} from './mock-http.js';
|
|
18
|
-
|
|
19
|
-
// Test Utilities
|
|
20
|
-
export {
|
|
21
|
-
createTestAmmo,
|
|
22
|
-
createEnhancedAmmo,
|
|
23
|
-
createTestRegistry,
|
|
24
|
-
createMockEndpoint,
|
|
25
|
-
createMockMiddleware,
|
|
26
|
-
createExpressStyleMiddleware,
|
|
27
|
-
sleep,
|
|
28
|
-
waitFor,
|
|
29
|
-
createMockDbConnection,
|
|
30
|
-
createMockFile,
|
|
31
|
-
createMockStorage,
|
|
32
|
-
assertResponse,
|
|
33
|
-
mockConsole,
|
|
34
|
-
captureResponse,
|
|
35
|
-
} from './test-utils.js';
|
|
36
|
-
|
|
37
|
-
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Mock HTTP IncomingMessage (request) object
|
|
5
|
-
* Simulates Node.js http.IncomingMessage for testing
|
|
6
|
-
*/
|
|
7
|
-
export class MockRequest extends EventEmitter {
|
|
8
|
-
constructor(options = {}) {
|
|
9
|
-
super();
|
|
10
|
-
|
|
11
|
-
this.method = options.method || 'GET';
|
|
12
|
-
this.url = options.url || '/';
|
|
13
|
-
this.headers = options.headers || {};
|
|
14
|
-
this.httpVersion = options.httpVersion || '1.1';
|
|
15
|
-
this.socket = options.socket || {
|
|
16
|
-
remoteAddress: options.ip || '127.0.0.1',
|
|
17
|
-
encrypted: options.encrypted || false,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
// Body handling
|
|
21
|
-
this._body = options.body || '';
|
|
22
|
-
this._bodyChunks = [];
|
|
23
|
-
|
|
24
|
-
// Connection info
|
|
25
|
-
this.connection = this.socket;
|
|
26
|
-
|
|
27
|
-
// Normalize headers to lowercase
|
|
28
|
-
this.headers = Object.keys(this.headers).reduce((acc, key) => {
|
|
29
|
-
acc[key.toLowerCase()] = this.headers[key];
|
|
30
|
-
return acc;
|
|
31
|
-
}, {});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Simulate incoming data by emitting chunks
|
|
36
|
-
*/
|
|
37
|
-
simulateBody(body) {
|
|
38
|
-
if (typeof body === 'string') {
|
|
39
|
-
this.emit('data', Buffer.from(body));
|
|
40
|
-
} else if (Buffer.isBuffer(body)) {
|
|
41
|
-
this.emit('data', body);
|
|
42
|
-
} else if (typeof body === 'object') {
|
|
43
|
-
this.emit('data', Buffer.from(JSON.stringify(body)));
|
|
44
|
-
}
|
|
45
|
-
this.emit('end');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Immediately emit body data (for sync tests)
|
|
50
|
-
*/
|
|
51
|
-
pipe(destination) {
|
|
52
|
-
if (this._body) {
|
|
53
|
-
destination.write(this._body);
|
|
54
|
-
}
|
|
55
|
-
return destination;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Mock HTTP ServerResponse (response) object
|
|
61
|
-
* Simulates Node.js http.ServerResponse for testing
|
|
62
|
-
*/
|
|
63
|
-
export class MockResponse extends EventEmitter {
|
|
64
|
-
constructor() {
|
|
65
|
-
super();
|
|
66
|
-
|
|
67
|
-
this.statusCode = 200;
|
|
68
|
-
this.statusMessage = '';
|
|
69
|
-
this.headers = {};
|
|
70
|
-
this._body = '';
|
|
71
|
-
this._chunks = [];
|
|
72
|
-
|
|
73
|
-
// Response state flags
|
|
74
|
-
this.headersSent = false;
|
|
75
|
-
this.writableEnded = false;
|
|
76
|
-
this.finished = false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Set response status code and headers
|
|
81
|
-
*/
|
|
82
|
-
writeHead(statusCode, statusMessage, headers) {
|
|
83
|
-
if (this.headersSent) {
|
|
84
|
-
throw new Error('Headers already sent');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this.statusCode = statusCode;
|
|
88
|
-
|
|
89
|
-
// Handle optional statusMessage
|
|
90
|
-
if (typeof statusMessage === 'object') {
|
|
91
|
-
headers = statusMessage;
|
|
92
|
-
statusMessage = '';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.statusMessage = statusMessage || '';
|
|
96
|
-
|
|
97
|
-
if (headers) {
|
|
98
|
-
Object.keys(headers).forEach((key) => {
|
|
99
|
-
this.headers[key.toLowerCase()] = headers[key];
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.headersSent = true;
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Set a single header
|
|
109
|
-
*/
|
|
110
|
-
setHeader(name, value) {
|
|
111
|
-
if (this.headersSent) {
|
|
112
|
-
throw new Error('Headers already sent');
|
|
113
|
-
}
|
|
114
|
-
this.headers[name.toLowerCase()] = value;
|
|
115
|
-
return this;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Get a header value
|
|
120
|
-
*/
|
|
121
|
-
getHeader(name) {
|
|
122
|
-
return this.headers[name.toLowerCase()];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Remove a header
|
|
127
|
-
*/
|
|
128
|
-
removeHeader(name) {
|
|
129
|
-
delete this.headers[name.toLowerCase()];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Check if a header has been set
|
|
134
|
-
*/
|
|
135
|
-
hasHeader(name) {
|
|
136
|
-
return name.toLowerCase() in this.headers;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Write data to response body
|
|
141
|
-
*/
|
|
142
|
-
write(chunk, encoding, callback) {
|
|
143
|
-
if (this.writableEnded) {
|
|
144
|
-
throw new Error('Write after end');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (chunk !== undefined && chunk !== null && chunk !== '') {
|
|
148
|
-
this._chunks.push(chunk);
|
|
149
|
-
this._body += typeof chunk === 'string' ? chunk : chunk.toString();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (typeof encoding === 'function') {
|
|
153
|
-
callback = encoding;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (typeof callback === 'function') {
|
|
157
|
-
callback();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* End the response
|
|
165
|
-
*/
|
|
166
|
-
end(data, encoding, callback) {
|
|
167
|
-
if (this.writableEnded) {
|
|
168
|
-
return this;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (typeof data === 'function') {
|
|
172
|
-
callback = data;
|
|
173
|
-
data = undefined;
|
|
174
|
-
} else if (typeof encoding === 'function') {
|
|
175
|
-
callback = encoding;
|
|
176
|
-
encoding = undefined;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (data !== undefined && data !== null && data !== '') {
|
|
180
|
-
this.write(data, encoding);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
this.writableEnded = true;
|
|
184
|
-
this.finished = true;
|
|
185
|
-
|
|
186
|
-
this.emit('finish');
|
|
187
|
-
|
|
188
|
-
if (typeof callback === 'function') {
|
|
189
|
-
callback();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return this;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Get the response body as a string
|
|
197
|
-
*/
|
|
198
|
-
getBody() {
|
|
199
|
-
return this._body;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Get the response body parsed as JSON
|
|
204
|
-
*/
|
|
205
|
-
getJsonBody() {
|
|
206
|
-
try {
|
|
207
|
-
return JSON.parse(this._body);
|
|
208
|
-
} catch {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Reset the response for reuse
|
|
215
|
-
*/
|
|
216
|
-
reset() {
|
|
217
|
-
this.statusCode = 200;
|
|
218
|
-
this.statusMessage = '';
|
|
219
|
-
this.headers = {};
|
|
220
|
-
this._body = '';
|
|
221
|
-
this._chunks = [];
|
|
222
|
-
this.headersSent = false;
|
|
223
|
-
this.writableEnded = false;
|
|
224
|
-
this.finished = false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Create a mock request with common defaults
|
|
230
|
-
*/
|
|
231
|
-
export function createMockRequest(options = {}) {
|
|
232
|
-
const defaults = {
|
|
233
|
-
method: 'GET',
|
|
234
|
-
url: '/',
|
|
235
|
-
headers: {
|
|
236
|
-
'host': 'localhost:3000',
|
|
237
|
-
'user-agent': 'test-agent',
|
|
238
|
-
'accept': '*/*',
|
|
239
|
-
},
|
|
240
|
-
ip: '127.0.0.1',
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
return new MockRequest({ ...defaults, ...options });
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Create a mock response
|
|
248
|
-
*/
|
|
249
|
-
export function createMockResponse() {
|
|
250
|
-
return new MockResponse();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Create a mock request/response pair
|
|
255
|
-
*/
|
|
256
|
-
export function createMockPair(requestOptions = {}) {
|
|
257
|
-
return {
|
|
258
|
-
req: createMockRequest(requestOptions),
|
|
259
|
-
res: createMockResponse(),
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Create a JSON request with proper headers
|
|
265
|
-
*/
|
|
266
|
-
export function createJsonRequest(options = {}) {
|
|
267
|
-
return createMockRequest({
|
|
268
|
-
...options,
|
|
269
|
-
headers: {
|
|
270
|
-
'content-type': 'application/json',
|
|
271
|
-
'accept': 'application/json',
|
|
272
|
-
...options.headers,
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Create a form-urlencoded request with proper headers
|
|
279
|
-
*/
|
|
280
|
-
export function createFormRequest(options = {}) {
|
|
281
|
-
return createMockRequest({
|
|
282
|
-
method: 'POST',
|
|
283
|
-
...options,
|
|
284
|
-
headers: {
|
|
285
|
-
'content-type': 'application/x-www-form-urlencoded',
|
|
286
|
-
...options.headers,
|
|
287
|
-
},
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Create a multipart form-data request with proper headers
|
|
293
|
-
*/
|
|
294
|
-
export function createMultipartRequest(boundary, options = {}) {
|
|
295
|
-
return createMockRequest({
|
|
296
|
-
method: 'POST',
|
|
297
|
-
...options,
|
|
298
|
-
headers: {
|
|
299
|
-
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
300
|
-
...options.headers,
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Helper to create multipart form body
|
|
307
|
-
*/
|
|
308
|
-
export function createMultipartBody(boundary, fields = [], files = []) {
|
|
309
|
-
let body = '';
|
|
310
|
-
|
|
311
|
-
// Add regular fields
|
|
312
|
-
for (const field of fields) {
|
|
313
|
-
body += `--${boundary}\r\n`;
|
|
314
|
-
body += `Content-Disposition: form-data; name="${field.name}"\r\n\r\n`;
|
|
315
|
-
body += `${field.value}\r\n`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Add files
|
|
319
|
-
for (const file of files) {
|
|
320
|
-
body += `--${boundary}\r\n`;
|
|
321
|
-
body += `Content-Disposition: form-data; name="${file.fieldName}"; filename="${file.filename}"\r\n`;
|
|
322
|
-
body += `Content-Type: ${file.contentType || 'application/octet-stream'}\r\n\r\n`;
|
|
323
|
-
body += `${file.content}\r\n`;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
body += `--${boundary}--\r\n`;
|
|
327
|
-
return body;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export default {
|
|
331
|
-
MockRequest,
|
|
332
|
-
MockResponse,
|
|
333
|
-
createMockRequest,
|
|
334
|
-
createMockResponse,
|
|
335
|
-
createMockPair,
|
|
336
|
-
createJsonRequest,
|
|
337
|
-
createFormRequest,
|
|
338
|
-
createMultipartRequest,
|
|
339
|
-
createMultipartBody,
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
|