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.
@@ -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
+ });
@@ -1,5 +1,5 @@
1
1
  import fetch from 'node-fetch';
2
- import { mockOpenAIResponse, stopMocking } from './main.js'
2
+ import { mockOpenAIResponse, stopMocking } from '../src/index.js';
3
3
  import OpenAI from 'openai';
4
4
 
5
5
  const openai = new OpenAI({ apiKey: "OPENAI_API_KEY" });
package/jest.config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  collectCoverageFrom: [
3
- 'main.js',
3
+ '/src/index.js',
4
4
  '!**/node_modules/**',
5
5
  ],
6
6
  coverageReporters: ['text', 'lcov'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openai-api-mock",
3
- "version": "0.1.23",
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": "main.js",
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
- ```js
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 force parameter is a boolean that determines whether the mock response should be used regardless of the environment.
42
- If force is true, the mock response will be used regardless of the environment.
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
- If force is `false` or not provided, the mock response will only be used if the `NODE_ENV` environment variable is set to `development`.
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 };