@friggframework/admin-scripts 2.0.0--canary.517.f04156f.0 → 2.0.0--canary.517.300ded3.0
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/index.js +2 -2
- package/package.json +6 -8
- package/src/application/__tests__/admin-frigg-commands.test.js +18 -18
- package/src/application/__tests__/script-runner.test.js +144 -14
- package/src/application/admin-frigg-commands.js +7 -7
- package/src/application/admin-script-base.js +2 -4
- package/src/application/script-runner.js +121 -127
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +32 -95
- package/src/infrastructure/__tests__/admin-script-router.test.js +24 -25
- package/src/infrastructure/admin-auth-middleware.js +5 -43
- package/src/infrastructure/admin-script-router.js +14 -16
- package/src/infrastructure/script-executor-handler.js +2 -2
- package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
- package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
- package/src/application/dry-run-http-interceptor.js +0 -296
- package/src/application/dry-run-repository-wrapper.js +0 -261
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
const { createDryRunWrapper, wrapAdminFriggCommandsForDryRun, sanitizeArgs } = require('../dry-run-repository-wrapper');
|
|
2
|
-
|
|
3
|
-
describe('Dry-Run Repository Wrapper', () => {
|
|
4
|
-
describe('createDryRunWrapper', () => {
|
|
5
|
-
let mockRepository;
|
|
6
|
-
let operationLog;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
operationLog = [];
|
|
10
|
-
mockRepository = {
|
|
11
|
-
// Read operations
|
|
12
|
-
findById: jest.fn(async (id) => ({ id, name: 'Test Entity' })),
|
|
13
|
-
findAll: jest.fn(async () => [{ id: '1' }, { id: '2' }]),
|
|
14
|
-
getStatus: jest.fn(() => 'active'),
|
|
15
|
-
|
|
16
|
-
// Write operations
|
|
17
|
-
create: jest.fn(async (data) => ({ id: 'new-id', ...data })),
|
|
18
|
-
update: jest.fn(async (id, data) => ({ id, ...data })),
|
|
19
|
-
delete: jest.fn(async (id) => ({ deletedCount: 1 })),
|
|
20
|
-
updateStatus: jest.fn(async (id, status) => ({ id, status })),
|
|
21
|
-
};
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test('should pass through read operations unchanged', async () => {
|
|
25
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
26
|
-
|
|
27
|
-
// Call read operations
|
|
28
|
-
const byId = await wrapped.findById('123');
|
|
29
|
-
const all = await wrapped.findAll();
|
|
30
|
-
const status = wrapped.getStatus();
|
|
31
|
-
|
|
32
|
-
// Verify original methods were called
|
|
33
|
-
expect(mockRepository.findById).toHaveBeenCalledWith('123');
|
|
34
|
-
expect(mockRepository.findAll).toHaveBeenCalled();
|
|
35
|
-
expect(mockRepository.getStatus).toHaveBeenCalled();
|
|
36
|
-
|
|
37
|
-
// Verify results match
|
|
38
|
-
expect(byId).toEqual({ id: '123', name: 'Test Entity' });
|
|
39
|
-
expect(all).toHaveLength(2);
|
|
40
|
-
expect(status).toBe('active');
|
|
41
|
-
|
|
42
|
-
// No operations should be logged
|
|
43
|
-
expect(operationLog).toHaveLength(0);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('should intercept and log write operations', async () => {
|
|
47
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
48
|
-
|
|
49
|
-
// Call write operations
|
|
50
|
-
await wrapped.create({ name: 'New Entity' });
|
|
51
|
-
await wrapped.update('123', { name: 'Updated' });
|
|
52
|
-
await wrapped.delete('456');
|
|
53
|
-
|
|
54
|
-
// Original write methods should NOT be called
|
|
55
|
-
expect(mockRepository.create).not.toHaveBeenCalled();
|
|
56
|
-
expect(mockRepository.update).not.toHaveBeenCalled();
|
|
57
|
-
expect(mockRepository.delete).not.toHaveBeenCalled();
|
|
58
|
-
|
|
59
|
-
// All operations should be logged
|
|
60
|
-
expect(operationLog).toHaveLength(3);
|
|
61
|
-
|
|
62
|
-
expect(operationLog[0]).toMatchObject({
|
|
63
|
-
operation: 'CREATE',
|
|
64
|
-
model: 'TestModel',
|
|
65
|
-
method: 'create',
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(operationLog[1]).toMatchObject({
|
|
69
|
-
operation: 'UPDATE',
|
|
70
|
-
model: 'TestModel',
|
|
71
|
-
method: 'update',
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
expect(operationLog[2]).toMatchObject({
|
|
75
|
-
operation: 'DELETE',
|
|
76
|
-
model: 'TestModel',
|
|
77
|
-
method: 'delete',
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('should return mock data for create operations', async () => {
|
|
82
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
83
|
-
|
|
84
|
-
const result = await wrapped.create({ name: 'Test', value: 42 });
|
|
85
|
-
|
|
86
|
-
expect(result).toMatchObject({
|
|
87
|
-
name: 'Test',
|
|
88
|
-
value: 42,
|
|
89
|
-
_dryRun: true,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
expect(result.id).toMatch(/^dry-run-/);
|
|
93
|
-
expect(result.createdAt).toBeDefined();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('should return mock data for update operations', async () => {
|
|
97
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
98
|
-
|
|
99
|
-
const result = await wrapped.update('123', { status: 'inactive' });
|
|
100
|
-
|
|
101
|
-
expect(result).toMatchObject({
|
|
102
|
-
id: '123',
|
|
103
|
-
status: 'inactive',
|
|
104
|
-
_dryRun: true,
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test('should return mock data for delete operations', async () => {
|
|
109
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
110
|
-
|
|
111
|
-
const result = await wrapped.delete('123');
|
|
112
|
-
|
|
113
|
-
expect(result).toEqual({
|
|
114
|
-
deletedCount: 1,
|
|
115
|
-
_dryRun: true,
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('should try to return existing data for updates when possible', async () => {
|
|
120
|
-
const wrapped = createDryRunWrapper(mockRepository, operationLog, 'TestModel');
|
|
121
|
-
|
|
122
|
-
const result = await wrapped.updateStatus('123', 'inactive');
|
|
123
|
-
|
|
124
|
-
// Should attempt to read existing data
|
|
125
|
-
expect(mockRepository.findById).toHaveBeenCalledWith('123');
|
|
126
|
-
|
|
127
|
-
// If found, should return existing merged with updates
|
|
128
|
-
expect(result.id).toBe('123');
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('sanitizeArgs', () => {
|
|
133
|
-
test('should redact sensitive fields in objects', () => {
|
|
134
|
-
const args = [
|
|
135
|
-
{
|
|
136
|
-
id: '123',
|
|
137
|
-
password: 'secret123',
|
|
138
|
-
token: 'abc-def-ghi',
|
|
139
|
-
apiKey: 'sk_live_123',
|
|
140
|
-
name: 'Test User',
|
|
141
|
-
},
|
|
142
|
-
];
|
|
143
|
-
|
|
144
|
-
const sanitized = sanitizeArgs(args);
|
|
145
|
-
|
|
146
|
-
expect(sanitized[0]).toEqual({
|
|
147
|
-
id: '123',
|
|
148
|
-
password: '[REDACTED]',
|
|
149
|
-
token: '[REDACTED]',
|
|
150
|
-
apiKey: '[REDACTED]',
|
|
151
|
-
name: 'Test User',
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('should handle nested objects', () => {
|
|
156
|
-
const args = [
|
|
157
|
-
{
|
|
158
|
-
user: {
|
|
159
|
-
name: 'Test',
|
|
160
|
-
credentials: {
|
|
161
|
-
password: 'secret',
|
|
162
|
-
apiToken: 'token123',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
];
|
|
167
|
-
|
|
168
|
-
const sanitized = sanitizeArgs(args);
|
|
169
|
-
|
|
170
|
-
expect(sanitized[0].user.name).toBe('Test');
|
|
171
|
-
expect(sanitized[0].user.credentials.password).toBe('[REDACTED]');
|
|
172
|
-
expect(sanitized[0].user.credentials.apiToken).toBe('[REDACTED]');
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
test('should handle arrays', () => {
|
|
176
|
-
const args = [
|
|
177
|
-
[
|
|
178
|
-
{ id: '1', token: 'abc' },
|
|
179
|
-
{ id: '2', secret: 'xyz' },
|
|
180
|
-
],
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
const sanitized = sanitizeArgs(args);
|
|
184
|
-
|
|
185
|
-
expect(sanitized[0][0].token).toBe('[REDACTED]');
|
|
186
|
-
expect(sanitized[0][1].secret).toBe('[REDACTED]');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('should preserve primitives', () => {
|
|
190
|
-
const args = ['string', 123, true, null, undefined];
|
|
191
|
-
const sanitized = sanitizeArgs(args);
|
|
192
|
-
|
|
193
|
-
expect(sanitized).toEqual(['string', 123, true, null, undefined]);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe('wrapAdminFriggCommandsForDryRun', () => {
|
|
198
|
-
let mockCommands;
|
|
199
|
-
let operationLog;
|
|
200
|
-
|
|
201
|
-
beforeEach(() => {
|
|
202
|
-
operationLog = [];
|
|
203
|
-
mockCommands = {
|
|
204
|
-
// Read operations
|
|
205
|
-
findIntegrationById: jest.fn(async (id) => ({ id, status: 'active' })),
|
|
206
|
-
listIntegrations: jest.fn(async () => []),
|
|
207
|
-
|
|
208
|
-
// Write operations
|
|
209
|
-
updateIntegrationConfig: jest.fn(async (id, config) => ({ id, config })),
|
|
210
|
-
updateIntegrationStatus: jest.fn(async (id, status) => ({ id, status })),
|
|
211
|
-
updateCredential: jest.fn(async (id, updates) => ({ id, ...updates })),
|
|
212
|
-
|
|
213
|
-
// Other methods
|
|
214
|
-
log: jest.fn(),
|
|
215
|
-
};
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('should pass through read operations', async () => {
|
|
219
|
-
const wrapped = wrapAdminFriggCommandsForDryRun(mockCommands, operationLog);
|
|
220
|
-
|
|
221
|
-
const integration = await wrapped.findIntegrationById('123');
|
|
222
|
-
const list = await wrapped.listIntegrations();
|
|
223
|
-
|
|
224
|
-
expect(mockCommands.findIntegrationById).toHaveBeenCalledWith('123');
|
|
225
|
-
expect(mockCommands.listIntegrations).toHaveBeenCalled();
|
|
226
|
-
|
|
227
|
-
expect(integration.id).toBe('123');
|
|
228
|
-
expect(operationLog).toHaveLength(0);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('should intercept write operations', async () => {
|
|
232
|
-
const wrapped = wrapAdminFriggCommandsForDryRun(mockCommands, operationLog);
|
|
233
|
-
|
|
234
|
-
await wrapped.updateIntegrationConfig('123', { setting: 'value' });
|
|
235
|
-
await wrapped.updateIntegrationStatus('456', 'inactive');
|
|
236
|
-
|
|
237
|
-
expect(mockCommands.updateIntegrationConfig).not.toHaveBeenCalled();
|
|
238
|
-
expect(mockCommands.updateIntegrationStatus).not.toHaveBeenCalled();
|
|
239
|
-
|
|
240
|
-
expect(operationLog).toHaveLength(2);
|
|
241
|
-
expect(operationLog[0].operation).toBe('UPDATEINTEGRATIONCONFIG');
|
|
242
|
-
expect(operationLog[1].operation).toBe('UPDATEINTEGRATIONSTATUS');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test('should return existing data for known update methods', async () => {
|
|
246
|
-
const wrapped = wrapAdminFriggCommandsForDryRun(mockCommands, operationLog);
|
|
247
|
-
|
|
248
|
-
const result = await wrapped.updateIntegrationConfig('123', { new: 'config' });
|
|
249
|
-
|
|
250
|
-
// Should have tried to fetch existing
|
|
251
|
-
expect(mockCommands.findIntegrationById).toHaveBeenCalledWith('123');
|
|
252
|
-
|
|
253
|
-
// Should return existing data
|
|
254
|
-
expect(result.id).toBe('123');
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
});
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dry-Run HTTP Interceptor
|
|
3
|
-
*
|
|
4
|
-
* Creates a mock HTTP client that logs requests instead of executing them.
|
|
5
|
-
* Used to intercept API module calls during dry-run.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Sanitize headers to remove authentication tokens
|
|
10
|
-
* @param {Object} headers - HTTP headers
|
|
11
|
-
* @returns {Object} Sanitized headers
|
|
12
|
-
*/
|
|
13
|
-
function sanitizeHeaders(headers) {
|
|
14
|
-
if (!headers || typeof headers !== 'object') {
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const safe = { ...headers };
|
|
19
|
-
|
|
20
|
-
// Remove common auth headers
|
|
21
|
-
const sensitiveHeaders = [
|
|
22
|
-
'authorization',
|
|
23
|
-
'Authorization',
|
|
24
|
-
'x-api-key',
|
|
25
|
-
'X-API-Key',
|
|
26
|
-
'x-auth-token',
|
|
27
|
-
'X-Auth-Token',
|
|
28
|
-
'api-key',
|
|
29
|
-
'API-Key',
|
|
30
|
-
'apikey',
|
|
31
|
-
'ApiKey',
|
|
32
|
-
'token',
|
|
33
|
-
'Token',
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
for (const header of sensitiveHeaders) {
|
|
37
|
-
if (safe[header]) {
|
|
38
|
-
safe[header] = '[REDACTED]';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return safe;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Detect service name from base URL
|
|
47
|
-
* @param {string} baseURL - Base URL of the API
|
|
48
|
-
* @returns {string} Service name
|
|
49
|
-
*/
|
|
50
|
-
function detectService(baseURL) {
|
|
51
|
-
if (!baseURL) return 'unknown';
|
|
52
|
-
|
|
53
|
-
const url = baseURL.toLowerCase();
|
|
54
|
-
|
|
55
|
-
// CRM Systems
|
|
56
|
-
if (url.includes('hubspot') || url.includes('hubapi')) return 'HubSpot';
|
|
57
|
-
if (url.includes('salesforce')) return 'Salesforce';
|
|
58
|
-
if (url.includes('pipedrive')) return 'Pipedrive';
|
|
59
|
-
if (url.includes('zoho')) return 'Zoho CRM';
|
|
60
|
-
if (url.includes('attio')) return 'Attio';
|
|
61
|
-
|
|
62
|
-
// Communication
|
|
63
|
-
if (url.includes('slack')) return 'Slack';
|
|
64
|
-
if (url.includes('discord')) return 'Discord';
|
|
65
|
-
if (url.includes('teams.microsoft')) return 'Microsoft Teams';
|
|
66
|
-
|
|
67
|
-
// Project Management
|
|
68
|
-
if (url.includes('asana')) return 'Asana';
|
|
69
|
-
if (url.includes('monday')) return 'Monday.com';
|
|
70
|
-
if (url.includes('trello')) return 'Trello';
|
|
71
|
-
if (url.includes('clickup')) return 'ClickUp';
|
|
72
|
-
|
|
73
|
-
// Storage
|
|
74
|
-
if (url.includes('googleapis.com/drive')) return 'Google Drive';
|
|
75
|
-
if (url.includes('dropbox')) return 'Dropbox';
|
|
76
|
-
if (url.includes('box.com')) return 'Box';
|
|
77
|
-
|
|
78
|
-
// Email & Marketing
|
|
79
|
-
if (url.includes('sendgrid')) return 'SendGrid';
|
|
80
|
-
if (url.includes('mailchimp')) return 'Mailchimp';
|
|
81
|
-
if (url.includes('gmail')) return 'Gmail';
|
|
82
|
-
|
|
83
|
-
// Accounting
|
|
84
|
-
if (url.includes('quickbooks')) return 'QuickBooks';
|
|
85
|
-
if (url.includes('xero')) return 'Xero';
|
|
86
|
-
|
|
87
|
-
// Other
|
|
88
|
-
if (url.includes('stripe')) return 'Stripe';
|
|
89
|
-
if (url.includes('shopify')) return 'Shopify';
|
|
90
|
-
if (url.includes('github')) return 'GitHub';
|
|
91
|
-
if (url.includes('gitlab')) return 'GitLab';
|
|
92
|
-
|
|
93
|
-
return 'unknown';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Sanitize request data to remove sensitive information
|
|
98
|
-
* @param {*} data - Request data
|
|
99
|
-
* @returns {*} Sanitized data
|
|
100
|
-
*/
|
|
101
|
-
function sanitizeData(data) {
|
|
102
|
-
if (data === null || data === undefined) {
|
|
103
|
-
return data;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (typeof data !== 'object') {
|
|
107
|
-
return data;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (Array.isArray(data)) {
|
|
111
|
-
return data.map(sanitizeData);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const sanitized = {};
|
|
115
|
-
for (const [key, value] of Object.entries(data)) {
|
|
116
|
-
const lowerKey = key.toLowerCase();
|
|
117
|
-
|
|
118
|
-
// Check if this is a leaf node that should be redacted
|
|
119
|
-
const isSensitiveField =
|
|
120
|
-
lowerKey === 'password' ||
|
|
121
|
-
lowerKey === 'token' ||
|
|
122
|
-
lowerKey === 'secret' ||
|
|
123
|
-
lowerKey === 'apikey' ||
|
|
124
|
-
lowerKey.endsWith('password') ||
|
|
125
|
-
lowerKey.endsWith('token') ||
|
|
126
|
-
lowerKey.endsWith('secret') ||
|
|
127
|
-
lowerKey.endsWith('key') && !lowerKey.endsWith('publickey');
|
|
128
|
-
|
|
129
|
-
// Only redact if it's a primitive value (not an object/array)
|
|
130
|
-
if (isSensitiveField && typeof value !== 'object') {
|
|
131
|
-
sanitized[key] = '[REDACTED]';
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Recursively sanitize nested objects
|
|
136
|
-
if (typeof value === 'object' && value !== null) {
|
|
137
|
-
sanitized[key] = sanitizeData(value);
|
|
138
|
-
} else {
|
|
139
|
-
sanitized[key] = value;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return sanitized;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create a dry-run HTTP client
|
|
148
|
-
*
|
|
149
|
-
* @param {Array} operationLog - Array to append logged HTTP requests
|
|
150
|
-
* @returns {Object} Mock HTTP client compatible with axios interface
|
|
151
|
-
*/
|
|
152
|
-
function createDryRunHttpClient(operationLog) {
|
|
153
|
-
/**
|
|
154
|
-
* Mock HTTP request handler
|
|
155
|
-
* @param {Object} config - Request configuration
|
|
156
|
-
* @returns {Promise<Object>} Mock response
|
|
157
|
-
*/
|
|
158
|
-
const mockRequest = async (config) => {
|
|
159
|
-
// Build full URL
|
|
160
|
-
let fullUrl = config.url;
|
|
161
|
-
if (config.baseURL && !config.url.startsWith('http')) {
|
|
162
|
-
fullUrl = `${config.baseURL}${config.url.startsWith('/') ? '' : '/'}${config.url}`;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Log the request that WOULD have been made
|
|
166
|
-
const logEntry = {
|
|
167
|
-
operation: 'HTTP_REQUEST',
|
|
168
|
-
method: (config.method || 'GET').toUpperCase(),
|
|
169
|
-
url: fullUrl,
|
|
170
|
-
baseURL: config.baseURL,
|
|
171
|
-
path: config.url,
|
|
172
|
-
service: detectService(config.baseURL || fullUrl),
|
|
173
|
-
headers: sanitizeHeaders(config.headers),
|
|
174
|
-
timestamp: new Date().toISOString(),
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Include request data for write operations
|
|
178
|
-
if (config.data && ['POST', 'PUT', 'PATCH'].includes(logEntry.method)) {
|
|
179
|
-
logEntry.data = sanitizeData(config.data);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Include query params
|
|
183
|
-
if (config.params) {
|
|
184
|
-
logEntry.params = sanitizeData(config.params);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
operationLog.push(logEntry);
|
|
188
|
-
|
|
189
|
-
// Return mock response
|
|
190
|
-
return {
|
|
191
|
-
status: 200,
|
|
192
|
-
statusText: 'OK (Dry-Run)',
|
|
193
|
-
data: {
|
|
194
|
-
_dryRun: true,
|
|
195
|
-
_message: 'This is a dry-run mock response',
|
|
196
|
-
_wouldHaveExecuted: `${logEntry.method} ${fullUrl}`,
|
|
197
|
-
_service: logEntry.service,
|
|
198
|
-
},
|
|
199
|
-
headers: {
|
|
200
|
-
'content-type': 'application/json',
|
|
201
|
-
'x-dry-run': 'true',
|
|
202
|
-
},
|
|
203
|
-
config,
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Return axios-compatible interface
|
|
208
|
-
return {
|
|
209
|
-
request: mockRequest,
|
|
210
|
-
get: (url, config = {}) => mockRequest({ ...config, method: 'GET', url }),
|
|
211
|
-
post: (url, data, config = {}) => mockRequest({ ...config, method: 'POST', url, data }),
|
|
212
|
-
put: (url, data, config = {}) => mockRequest({ ...config, method: 'PUT', url, data }),
|
|
213
|
-
patch: (url, data, config = {}) =>
|
|
214
|
-
mockRequest({ ...config, method: 'PATCH', url, data }),
|
|
215
|
-
delete: (url, config = {}) => mockRequest({ ...config, method: 'DELETE', url }),
|
|
216
|
-
head: (url, config = {}) => mockRequest({ ...config, method: 'HEAD', url }),
|
|
217
|
-
options: (url, config = {}) => mockRequest({ ...config, method: 'OPTIONS', url }),
|
|
218
|
-
|
|
219
|
-
// Axios-specific properties
|
|
220
|
-
defaults: {
|
|
221
|
-
headers: {
|
|
222
|
-
common: {},
|
|
223
|
-
get: {},
|
|
224
|
-
post: {},
|
|
225
|
-
put: {},
|
|
226
|
-
patch: {},
|
|
227
|
-
delete: {},
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
// Interceptors (no-op in dry-run)
|
|
232
|
-
interceptors: {
|
|
233
|
-
request: { use: () => {}, eject: () => {} },
|
|
234
|
-
response: { use: () => {}, eject: () => {} },
|
|
235
|
-
},
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Inject dry-run HTTP client into an integration instance
|
|
241
|
-
*
|
|
242
|
-
* @param {Object} integrationInstance - Integration instance from integrationFactory
|
|
243
|
-
* @param {Object} dryRunHttpClient - Dry-run HTTP client
|
|
244
|
-
*/
|
|
245
|
-
function injectDryRunHttpClient(integrationInstance, dryRunHttpClient) {
|
|
246
|
-
if (!integrationInstance) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Inject into primary API module
|
|
251
|
-
if (integrationInstance.primary?.api) {
|
|
252
|
-
injectIntoApiModule(integrationInstance.primary.api, dryRunHttpClient);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Inject into target API module
|
|
256
|
-
if (integrationInstance.target?.api) {
|
|
257
|
-
injectIntoApiModule(integrationInstance.target.api, dryRunHttpClient);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Inject dry-run HTTP client into an API module
|
|
263
|
-
* @param {Object} apiModule - API module instance
|
|
264
|
-
* @param {Object} dryRunHttpClient - Dry-run HTTP client
|
|
265
|
-
*/
|
|
266
|
-
function injectIntoApiModule(apiModule, dryRunHttpClient) {
|
|
267
|
-
// Common property names for HTTP clients in API modules
|
|
268
|
-
const httpClientProps = [
|
|
269
|
-
'_httpClient',
|
|
270
|
-
'httpClient',
|
|
271
|
-
'client',
|
|
272
|
-
'axios',
|
|
273
|
-
'request',
|
|
274
|
-
'api',
|
|
275
|
-
'http',
|
|
276
|
-
];
|
|
277
|
-
|
|
278
|
-
for (const prop of httpClientProps) {
|
|
279
|
-
if (apiModule[prop] && typeof apiModule[prop] === 'object') {
|
|
280
|
-
apiModule[prop] = dryRunHttpClient;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Also check if the API module itself has request methods
|
|
285
|
-
if (typeof apiModule.request === 'function') {
|
|
286
|
-
Object.assign(apiModule, dryRunHttpClient);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
module.exports = {
|
|
291
|
-
createDryRunHttpClient,
|
|
292
|
-
injectDryRunHttpClient,
|
|
293
|
-
sanitizeHeaders,
|
|
294
|
-
sanitizeData,
|
|
295
|
-
detectService,
|
|
296
|
-
};
|