openai-api-mock 0.1.24 → 0.1.25
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/__tests__/custom_endpoint.test.js +47 -0
- package/{src → __tests__}/index.test.js +1 -1
- package/package.json +1 -1
- package/readme.md +1 -1
- package/src/index.js +30 -25
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { mockOpenAIResponse, stopMocking } from '../src/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Mock Custom OpenAI endpoint', () => {
|
|
5
|
+
let mock;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
mock = mockOpenAIResponse(true, { logRequests: true });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
stopMocking();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('should mock custom endpoint', async () => {
|
|
15
|
+
mock.addCustomEndpoint('POST', '/v1/custom/endpoint', () => {
|
|
16
|
+
return [200, { message: 'Custom endpoint response' }];
|
|
17
|
+
});
|
|
18
|
+
const response = await fetch('https://api.openai.com/v1/custom/endpoint'
|
|
19
|
+
, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
expect(data).toEqual({ message: 'Custom endpoint response' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should mock custom endpoint with error', async () => {
|
|
32
|
+
mock.addCustomEndpoint('POST', '/v1/custom/endpoint', () => {
|
|
33
|
+
return [400, { error: { message: 'Invalid request' } }];
|
|
34
|
+
});
|
|
35
|
+
const response = await fetch('https://api.openai.com/v1/custom/endpoint'
|
|
36
|
+
, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
expect(data).toEqual({ error: { message: 'Invalid request' } });
|
|
46
|
+
});
|
|
47
|
+
});
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -61,7 +61,7 @@ console.log(mock.isActive);
|
|
|
61
61
|
// Stop all mocks
|
|
62
62
|
mock.stopMocking();
|
|
63
63
|
|
|
64
|
-
// Add custom endpoint mock
|
|
64
|
+
// Add custom endpoint mock (uses api.openai.com as base url)
|
|
65
65
|
mock.addCustomEndpoint('POST', '/v1/custom', (uri, body) => {
|
|
66
66
|
return [200, { custom: 'response' }];
|
|
67
67
|
});
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { getImageResponce } from './image.js';
|
|
|
6
6
|
const OPEN_AI_BASE_URL = 'https://api.openai.com';
|
|
7
7
|
const CHAT_COMPLETIONS_ENDPOINT = '/v1/chat/completions';
|
|
8
8
|
const IMAGE_GENERATIONS_ENDPOINT = '/v1/images/generations';
|
|
9
|
-
|
|
9
|
+
const customScopes = []; // Track custom scopes
|
|
10
10
|
/**
|
|
11
11
|
* Mock OpenAI API responses
|
|
12
12
|
* @param {boolean} force - Force mocking regardless of environment
|
|
@@ -22,18 +22,18 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
22
22
|
latency = 0,
|
|
23
23
|
logRequests = false
|
|
24
24
|
} = options;
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
const env = process.env.NODE_ENV || 'development';
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// Only proceed if in development or forced
|
|
29
29
|
if (env !== 'development' && !force) {
|
|
30
30
|
return { isActive: false, stopMocking };
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
// Add delay if latency is specified
|
|
34
|
-
const delayResponse = latency > 0 ?
|
|
34
|
+
const delayResponse = latency > 0 ?
|
|
35
35
|
nock.defaults({ delayConnection: latency }) : null;
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
// Mock chat completions endpoint
|
|
38
38
|
const chatScope = nock(OPEN_AI_BASE_URL)
|
|
39
39
|
.post(CHAT_COMPLETIONS_ENDPOINT)
|
|
@@ -41,32 +41,32 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
41
41
|
if (logRequests) {
|
|
42
42
|
console.log(`[openai-api-mock] Chat request:`, JSON.stringify(requestBody, null, 2));
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
try {
|
|
46
46
|
// Validate minimal required fields
|
|
47
47
|
if (!requestBody.model || !requestBody.messages || !Array.isArray(requestBody.messages)) {
|
|
48
48
|
return [400, { error: { message: 'Invalid request. Missing required fields.' } }];
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
// Simulate random errors if enabled
|
|
52
52
|
if (includeErrors && Math.random() < 0.05) {
|
|
53
53
|
return [429, { error: { message: 'Rate limit exceeded' } }];
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
const isSteaming = requestBody.stream === true;
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if (isSteaming) {
|
|
59
59
|
const stream = createChatStream(requestBody);
|
|
60
60
|
return [200, stream];
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
return [200, getChatResponce(requestBody)];
|
|
64
64
|
} catch (error) {
|
|
65
65
|
console.error('[openai-api-mock] Error processing chat request:', error);
|
|
66
66
|
return [500, { error: { message: 'Internal server error in mock' } }];
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
// Mock image generations endpoint
|
|
71
71
|
const imageScope = nock(OPEN_AI_BASE_URL)
|
|
72
72
|
.post(IMAGE_GENERATIONS_ENDPOINT)
|
|
@@ -74,49 +74,54 @@ function mockOpenAIResponse(force = false, options = {}) {
|
|
|
74
74
|
if (logRequests) {
|
|
75
75
|
console.log(`[openai-api-mock] Image request:`, JSON.stringify(requestBody, null, 2));
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
try {
|
|
79
79
|
// Validate minimal required fields
|
|
80
80
|
if (!requestBody.prompt) {
|
|
81
81
|
return [400, { error: { message: 'Invalid request. Missing prompt.' } }];
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
// Simulate random errors if enabled
|
|
85
85
|
if (includeErrors && Math.random() < 0.05) {
|
|
86
86
|
return [400, { error: { message: 'Your request was rejected as a result of our safety system.' } }];
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
return [200, getImageResponce(requestBody)];
|
|
90
90
|
} catch (error) {
|
|
91
91
|
console.error('[openai-api-mock] Error processing image request:', error);
|
|
92
92
|
return [500, { error: { message: 'Internal server error in mock' } }];
|
|
93
93
|
}
|
|
94
94
|
});
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
// Enable other network connections
|
|
97
97
|
nock.enableNetConnect(host => host !== "api.openai.com");
|
|
98
|
-
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
99
101
|
return {
|
|
100
102
|
isActive: true,
|
|
101
103
|
stopMocking,
|
|
102
|
-
|
|
104
|
+
|
|
103
105
|
/**
|
|
104
106
|
* Adds a custom endpoint mock
|
|
105
|
-
* @param {string} method - HTTP method (GET, POST
|
|
106
|
-
* @param {string} path -
|
|
107
|
-
* @param {Function} handler -
|
|
107
|
+
* @param {string} method - HTTP method (e.g., 'GET', 'POST')
|
|
108
|
+
* @param {string} path - Endpoint path (e.g., '/v1/custom')
|
|
109
|
+
* @param {Function} handler - Function returning [statusCode, responseBody]
|
|
108
110
|
*/
|
|
109
111
|
addCustomEndpoint(method, path, handler) {
|
|
110
|
-
|
|
112
|
+
const methodLower = method.toLowerCase();
|
|
113
|
+
const scope = nock(OPEN_AI_BASE_URL)
|
|
114
|
+
[methodLower](path)
|
|
115
|
+
.reply(handler)
|
|
116
|
+
.persist(); // Keep the mock active indefinitely
|
|
117
|
+
customScopes.push(scope);
|
|
111
118
|
}
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
/**
|
|
116
|
-
* Stop all active mocks
|
|
117
|
-
*/
|
|
118
122
|
function stopMocking() {
|
|
119
123
|
nock.cleanAll();
|
|
124
|
+
customScopes.forEach(scope => scope.persist(false)); // Disable persistence
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
export { mockOpenAIResponse, stopMocking };
|