openai-api-mock 0.1.23 → 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/{main.test.js → __tests__/index.test.js} +1 -1
- package/jest.config.js +1 -1
- package/package.json +2 -2
- package/readme.md +30 -6
- package/src/index.js +127 -0
- package/main.js +0 -42
|
@@ -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/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openai-api-mock",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "A Node.js module for mocking OpenAI API responses in a development environment",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"author": "Chiheb Nabil",
|
|
30
30
|
"license": "MIT",
|
|
31
|
-
"main": "
|
|
31
|
+
"main": "./src/index.js",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@faker-js/faker": "^9.0.0",
|
|
34
34
|
"nock": "^13.5.4"
|
package/readme.md
CHANGED
|
@@ -30,18 +30,42 @@ import { mockOpenAIResponse } from 'openai-api-mock';
|
|
|
30
30
|
Then, call the mockOpenAIResponse function to set up the mock response:
|
|
31
31
|
|
|
32
32
|
```js
|
|
33
|
+
// Basic usage
|
|
33
34
|
mockOpenAIResponse();
|
|
34
|
-
```
|
|
35
|
-
This function intercepts HTTP calls to the OpenAI endpoint and returns a mock response. The mock response is generated based on the requestBody of the code, and it supports complex (function call) requestBody structures like arrays and nested objects.
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
// Force mocking regardless of environment
|
|
38
37
|
mockOpenAIResponse(true);
|
|
38
|
+
|
|
39
|
+
// With configuration options
|
|
40
|
+
mockOpenAIResponse(false, {
|
|
41
|
+
includeErrors: true, // Simulate random API errors
|
|
42
|
+
latency: 1000, // Add 1 second delay to responses
|
|
43
|
+
logRequests: true // Log incoming requests to console
|
|
44
|
+
});
|
|
39
45
|
```
|
|
40
46
|
|
|
41
|
-
The
|
|
42
|
-
|
|
47
|
+
The function accepts two parameters:
|
|
48
|
+
- `force` (boolean): Determines whether the mock response should be used regardless of the environment. If false or not provided, mocking only occurs in development environment.
|
|
49
|
+
- `options` (object): Additional configuration options
|
|
50
|
+
- `includeErrors` (boolean): When true, randomly simulates API errors
|
|
51
|
+
- `latency` (number): Adds artificial delay to responses in milliseconds
|
|
52
|
+
- `logRequests` (boolean): Logs incoming requests to console for debugging
|
|
53
|
+
|
|
54
|
+
The function returns an object with control methods:
|
|
55
|
+
```js
|
|
56
|
+
const mock = mockOpenAIResponse();
|
|
57
|
+
|
|
58
|
+
// Check if mocking is active
|
|
59
|
+
console.log(mock.isActive);
|
|
60
|
+
|
|
61
|
+
// Stop all mocks
|
|
62
|
+
mock.stopMocking();
|
|
43
63
|
|
|
44
|
-
|
|
64
|
+
// Add custom endpoint mock (uses api.openai.com as base url)
|
|
65
|
+
mock.addCustomEndpoint('POST', '/v1/custom', (uri, body) => {
|
|
66
|
+
return [200, { custom: 'response' }];
|
|
67
|
+
});
|
|
68
|
+
```
|
|
45
69
|
|
|
46
70
|
### Example responses
|
|
47
71
|
|
package/src/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import nock from 'nock';
|
|
2
|
+
import { getChatResponce } from './chat.js';
|
|
3
|
+
import { createChatStream } from './chat.stream.js';
|
|
4
|
+
import { getImageResponce } from './image.js';
|
|
5
|
+
|
|
6
|
+
const OPEN_AI_BASE_URL = 'https://api.openai.com';
|
|
7
|
+
const CHAT_COMPLETIONS_ENDPOINT = '/v1/chat/completions';
|
|
8
|
+
const IMAGE_GENERATIONS_ENDPOINT = '/v1/images/generations';
|
|
9
|
+
const customScopes = []; // Track custom scopes
|
|
10
|
+
/**
|
|
11
|
+
* Mock OpenAI API responses
|
|
12
|
+
* @param {boolean} force - Force mocking regardless of environment
|
|
13
|
+
* @param {Object} options - Additional configuration options
|
|
14
|
+
* @param {boolean} options.includeErrors - Whether to include error scenarios in mocking
|
|
15
|
+
* @param {number} options.latency - Artificial latency in ms to simulate network delay
|
|
16
|
+
* @param {boolean} options.logRequests - Whether to log incoming requests
|
|
17
|
+
* @returns {Object} An object with control methods for the mocks
|
|
18
|
+
*/
|
|
19
|
+
function mockOpenAIResponse(force = false, options = {}) {
|
|
20
|
+
const {
|
|
21
|
+
includeErrors = false,
|
|
22
|
+
latency = 0,
|
|
23
|
+
logRequests = false
|
|
24
|
+
} = options;
|
|
25
|
+
|
|
26
|
+
const env = process.env.NODE_ENV || 'development';
|
|
27
|
+
|
|
28
|
+
// Only proceed if in development or forced
|
|
29
|
+
if (env !== 'development' && !force) {
|
|
30
|
+
return { isActive: false, stopMocking };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Add delay if latency is specified
|
|
34
|
+
const delayResponse = latency > 0 ?
|
|
35
|
+
nock.defaults({ delayConnection: latency }) : null;
|
|
36
|
+
|
|
37
|
+
// Mock chat completions endpoint
|
|
38
|
+
const chatScope = nock(OPEN_AI_BASE_URL)
|
|
39
|
+
.post(CHAT_COMPLETIONS_ENDPOINT)
|
|
40
|
+
.reply(function (uri, requestBody) {
|
|
41
|
+
if (logRequests) {
|
|
42
|
+
console.log(`[openai-api-mock] Chat request:`, JSON.stringify(requestBody, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// Validate minimal required fields
|
|
47
|
+
if (!requestBody.model || !requestBody.messages || !Array.isArray(requestBody.messages)) {
|
|
48
|
+
return [400, { error: { message: 'Invalid request. Missing required fields.' } }];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Simulate random errors if enabled
|
|
52
|
+
if (includeErrors && Math.random() < 0.05) {
|
|
53
|
+
return [429, { error: { message: 'Rate limit exceeded' } }];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const isSteaming = requestBody.stream === true;
|
|
57
|
+
|
|
58
|
+
if (isSteaming) {
|
|
59
|
+
const stream = createChatStream(requestBody);
|
|
60
|
+
return [200, stream];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [200, getChatResponce(requestBody)];
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('[openai-api-mock] Error processing chat request:', error);
|
|
66
|
+
return [500, { error: { message: 'Internal server error in mock' } }];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Mock image generations endpoint
|
|
71
|
+
const imageScope = nock(OPEN_AI_BASE_URL)
|
|
72
|
+
.post(IMAGE_GENERATIONS_ENDPOINT)
|
|
73
|
+
.reply(function (uri, requestBody) {
|
|
74
|
+
if (logRequests) {
|
|
75
|
+
console.log(`[openai-api-mock] Image request:`, JSON.stringify(requestBody, null, 2));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
// Validate minimal required fields
|
|
80
|
+
if (!requestBody.prompt) {
|
|
81
|
+
return [400, { error: { message: 'Invalid request. Missing prompt.' } }];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Simulate random errors if enabled
|
|
85
|
+
if (includeErrors && Math.random() < 0.05) {
|
|
86
|
+
return [400, { error: { message: 'Your request was rejected as a result of our safety system.' } }];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [200, getImageResponce(requestBody)];
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('[openai-api-mock] Error processing image request:', error);
|
|
92
|
+
return [500, { error: { message: 'Internal server error in mock' } }];
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Enable other network connections
|
|
97
|
+
nock.enableNetConnect(host => host !== "api.openai.com");
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
isActive: true,
|
|
103
|
+
stopMocking,
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Adds a custom endpoint mock
|
|
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]
|
|
110
|
+
*/
|
|
111
|
+
addCustomEndpoint(method, path, handler) {
|
|
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);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function stopMocking() {
|
|
123
|
+
nock.cleanAll();
|
|
124
|
+
customScopes.forEach(scope => scope.persist(false)); // Disable persistence
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { mockOpenAIResponse, stopMocking };
|
package/main.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import nock from 'nock';
|
|
2
|
-
import { getChatResponce } from './src/chat.js';
|
|
3
|
-
import { createChatStream } from './src/chat.stream.js';
|
|
4
|
-
import { getImageResponce } from './src/image.js';
|
|
5
|
-
|
|
6
|
-
const OPEN_AI_BASE_URL = 'https://api.openai.com';
|
|
7
|
-
const CHAT_COMPLETIONS_ENDPOINT = '/v1/chat/completions';
|
|
8
|
-
const IMAGE_GENERATIONS_ENDPOINT = '/v1/images/generations';
|
|
9
|
-
|
|
10
|
-
function mockOpenAIResponse(force = false) {
|
|
11
|
-
var env = process.env.NODE_ENV || 'development';
|
|
12
|
-
// Intercept the HTTP call and return the mock response
|
|
13
|
-
if (env === 'development' || force) {
|
|
14
|
-
nock(OPEN_AI_BASE_URL)
|
|
15
|
-
.post(CHAT_COMPLETIONS_ENDPOINT)
|
|
16
|
-
.reply(function (uri, requestBody) {
|
|
17
|
-
let isSteaming = (requestBody.stream && requestBody.stream == true) ? true : false
|
|
18
|
-
|
|
19
|
-
if (isSteaming) {
|
|
20
|
-
const stream = createChatStream();
|
|
21
|
-
return [200, stream];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return [200, getChatResponce(requestBody)];
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
nock(OPEN_AI_BASE_URL)
|
|
28
|
-
.post(IMAGE_GENERATIONS_ENDPOINT)
|
|
29
|
-
.reply(function (uri, requestBody) {
|
|
30
|
-
return [200, getImageResponce(requestBody)];
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Mocking only the chat completion endpoint, not blocking other requests
|
|
34
|
-
nock.enableNetConnect(host => host !== "api.openai.com");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function stopMocking() {
|
|
39
|
-
nock.cleanAll();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export { mockOpenAIResponse, stopMocking };
|