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.
@@ -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 './index.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openai-api-mock",
3
- "version": "0.1.24",
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",
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, etc.)
106
- * @param {string} path - Path to mock (e.g., '/v1/custom')
107
- * @param {Function} handler - Handler function that returns [statusCode, responseBody]
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
- nock(OPEN_AI_BASE_URL)[method.toLowerCase()](path).reply(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);
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 };