@friggframework/admin-scripts 2.0.0--canary.517.a37d697.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/package.json +6 -8
- package/src/application/__tests__/script-runner.test.js +132 -2
- package/src/application/script-runner.js +120 -126
- 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,313 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
createDryRunHttpClient,
|
|
3
|
-
injectDryRunHttpClient,
|
|
4
|
-
sanitizeHeaders,
|
|
5
|
-
sanitizeData,
|
|
6
|
-
detectService,
|
|
7
|
-
} = require('../dry-run-http-interceptor');
|
|
8
|
-
|
|
9
|
-
describe('Dry-Run HTTP Interceptor', () => {
|
|
10
|
-
describe('sanitizeHeaders', () => {
|
|
11
|
-
test('should redact authorization headers', () => {
|
|
12
|
-
const headers = {
|
|
13
|
-
'Content-Type': 'application/json',
|
|
14
|
-
Authorization: 'Bearer secret-token',
|
|
15
|
-
'X-API-Key': 'api-key-123',
|
|
16
|
-
'User-Agent': 'frigg/1.0',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const sanitized = sanitizeHeaders(headers);
|
|
20
|
-
|
|
21
|
-
expect(sanitized['Content-Type']).toBe('application/json');
|
|
22
|
-
expect(sanitized['User-Agent']).toBe('frigg/1.0');
|
|
23
|
-
expect(sanitized.Authorization).toBe('[REDACTED]');
|
|
24
|
-
expect(sanitized['X-API-Key']).toBe('[REDACTED]');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('should handle case variations', () => {
|
|
28
|
-
const headers = {
|
|
29
|
-
authorization: 'Bearer token',
|
|
30
|
-
Authorization: 'Bearer token',
|
|
31
|
-
'x-api-key': 'key1',
|
|
32
|
-
'X-API-Key': 'key2',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const sanitized = sanitizeHeaders(headers);
|
|
36
|
-
|
|
37
|
-
expect(sanitized.authorization).toBe('[REDACTED]');
|
|
38
|
-
expect(sanitized.Authorization).toBe('[REDACTED]');
|
|
39
|
-
expect(sanitized['x-api-key']).toBe('[REDACTED]');
|
|
40
|
-
expect(sanitized['X-API-Key']).toBe('[REDACTED]');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('should handle null/undefined', () => {
|
|
44
|
-
expect(sanitizeHeaders(null)).toEqual({});
|
|
45
|
-
expect(sanitizeHeaders(undefined)).toEqual({});
|
|
46
|
-
expect(sanitizeHeaders({})).toEqual({});
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('detectService', () => {
|
|
51
|
-
test('should detect CRM services', () => {
|
|
52
|
-
expect(detectService('https://api.hubapi.com')).toBe('HubSpot');
|
|
53
|
-
expect(detectService('https://login.salesforce.com')).toBe('Salesforce');
|
|
54
|
-
expect(detectService('https://api.pipedrive.com')).toBe('Pipedrive');
|
|
55
|
-
expect(detectService('https://api.attio.com')).toBe('Attio');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('should detect communication services', () => {
|
|
59
|
-
expect(detectService('https://slack.com/api')).toBe('Slack');
|
|
60
|
-
expect(detectService('https://discord.com/api')).toBe('Discord');
|
|
61
|
-
expect(detectService('https://graph.teams.microsoft.com')).toBe('Microsoft Teams');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('should detect project management tools', () => {
|
|
65
|
-
expect(detectService('https://app.asana.com/api')).toBe('Asana');
|
|
66
|
-
expect(detectService('https://api.monday.com')).toBe('Monday.com');
|
|
67
|
-
expect(detectService('https://api.trello.com')).toBe('Trello');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('should return unknown for unrecognized services', () => {
|
|
71
|
-
expect(detectService('https://example.com/api')).toBe('unknown');
|
|
72
|
-
expect(detectService(null)).toBe('unknown');
|
|
73
|
-
expect(detectService(undefined)).toBe('unknown');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('should be case insensitive', () => {
|
|
77
|
-
expect(detectService('HTTPS://API.HUBSPOT.COM')).toBe('HubSpot');
|
|
78
|
-
expect(detectService('https://API.SLACK.COM')).toBe('Slack');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('sanitizeData', () => {
|
|
83
|
-
test('should redact sensitive fields', () => {
|
|
84
|
-
const data = {
|
|
85
|
-
name: 'Test User',
|
|
86
|
-
email: 'test@example.com',
|
|
87
|
-
password: 'secret123',
|
|
88
|
-
apiToken: 'token-abc',
|
|
89
|
-
authKey: 'key-xyz',
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const sanitized = sanitizeData(data);
|
|
93
|
-
|
|
94
|
-
expect(sanitized.name).toBe('Test User');
|
|
95
|
-
expect(sanitized.email).toBe('test@example.com');
|
|
96
|
-
expect(sanitized.password).toBe('[REDACTED]');
|
|
97
|
-
expect(sanitized.apiToken).toBe('[REDACTED]');
|
|
98
|
-
expect(sanitized.authKey).toBe('[REDACTED]');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('should handle nested objects', () => {
|
|
102
|
-
const data = {
|
|
103
|
-
user: {
|
|
104
|
-
name: 'Test',
|
|
105
|
-
credentials: {
|
|
106
|
-
password: 'secret',
|
|
107
|
-
token: 'abc123',
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const sanitized = sanitizeData(data);
|
|
113
|
-
|
|
114
|
-
expect(sanitized.user.name).toBe('Test');
|
|
115
|
-
expect(sanitized.user.credentials.password).toBe('[REDACTED]');
|
|
116
|
-
expect(sanitized.user.credentials.token).toBe('[REDACTED]');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test('should handle arrays', () => {
|
|
120
|
-
const data = [
|
|
121
|
-
{ id: '1', password: 'secret1' },
|
|
122
|
-
{ id: '2', apiKey: 'key2' },
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
const sanitized = sanitizeData(data);
|
|
126
|
-
|
|
127
|
-
expect(sanitized[0].id).toBe('1');
|
|
128
|
-
expect(sanitized[0].password).toBe('[REDACTED]');
|
|
129
|
-
expect(sanitized[1].apiKey).toBe('[REDACTED]');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('should preserve primitives', () => {
|
|
133
|
-
expect(sanitizeData('string')).toBe('string');
|
|
134
|
-
expect(sanitizeData(123)).toBe(123);
|
|
135
|
-
expect(sanitizeData(true)).toBe(true);
|
|
136
|
-
expect(sanitizeData(null)).toBe(null);
|
|
137
|
-
expect(sanitizeData(undefined)).toBe(undefined);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('createDryRunHttpClient', () => {
|
|
142
|
-
let operationLog;
|
|
143
|
-
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
operationLog = [];
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test('should log GET requests', async () => {
|
|
149
|
-
const client = createDryRunHttpClient(operationLog);
|
|
150
|
-
|
|
151
|
-
const response = await client.get('/contacts', {
|
|
152
|
-
baseURL: 'https://api.hubapi.com',
|
|
153
|
-
headers: { Authorization: 'Bearer token' },
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
expect(operationLog).toHaveLength(1);
|
|
157
|
-
expect(operationLog[0]).toMatchObject({
|
|
158
|
-
operation: 'HTTP_REQUEST',
|
|
159
|
-
method: 'GET',
|
|
160
|
-
url: 'https://api.hubapi.com/contacts',
|
|
161
|
-
service: 'HubSpot',
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
expect(operationLog[0].headers.Authorization).toBe('[REDACTED]');
|
|
165
|
-
expect(response.data._dryRun).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test('should log POST requests with data', async () => {
|
|
169
|
-
const client = createDryRunHttpClient(operationLog);
|
|
170
|
-
|
|
171
|
-
const postData = {
|
|
172
|
-
name: 'John Doe',
|
|
173
|
-
email: 'john@example.com',
|
|
174
|
-
password: 'secret123',
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
await client.post('/users', postData, {
|
|
178
|
-
baseURL: 'https://api.example.com',
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
expect(operationLog).toHaveLength(1);
|
|
182
|
-
expect(operationLog[0].method).toBe('POST');
|
|
183
|
-
expect(operationLog[0].data.name).toBe('John Doe');
|
|
184
|
-
expect(operationLog[0].data.email).toBe('john@example.com');
|
|
185
|
-
expect(operationLog[0].data.password).toBe('[REDACTED]');
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('should log PUT requests', async () => {
|
|
189
|
-
const client = createDryRunHttpClient(operationLog);
|
|
190
|
-
|
|
191
|
-
await client.put('/users/123', { status: 'active' }, {
|
|
192
|
-
baseURL: 'https://api.example.com',
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
expect(operationLog).toHaveLength(1);
|
|
196
|
-
expect(operationLog[0].method).toBe('PUT');
|
|
197
|
-
expect(operationLog[0].data.status).toBe('active');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test('should log PATCH requests', async () => {
|
|
201
|
-
const client = createDryRunHttpClient(operationLog);
|
|
202
|
-
|
|
203
|
-
await client.patch('/users/123', { name: 'Updated' });
|
|
204
|
-
|
|
205
|
-
expect(operationLog).toHaveLength(1);
|
|
206
|
-
expect(operationLog[0].method).toBe('PATCH');
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test('should log DELETE requests', async () => {
|
|
210
|
-
const client = createDryRunHttpClient(operationLog);
|
|
211
|
-
|
|
212
|
-
await client.delete('/users/123', {
|
|
213
|
-
baseURL: 'https://api.example.com',
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
expect(operationLog).toHaveLength(1);
|
|
217
|
-
expect(operationLog[0].method).toBe('DELETE');
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test('should return mock response', async () => {
|
|
221
|
-
const client = createDryRunHttpClient(operationLog);
|
|
222
|
-
|
|
223
|
-
const response = await client.get('/test');
|
|
224
|
-
|
|
225
|
-
expect(response.status).toBe(200);
|
|
226
|
-
expect(response.statusText).toContain('Dry-Run');
|
|
227
|
-
expect(response.data._dryRun).toBe(true);
|
|
228
|
-
expect(response.headers['x-dry-run']).toBe('true');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('should include query params in log', async () => {
|
|
232
|
-
const client = createDryRunHttpClient(operationLog);
|
|
233
|
-
|
|
234
|
-
await client.get('/search', {
|
|
235
|
-
baseURL: 'https://api.example.com',
|
|
236
|
-
params: { q: 'test', limit: 10 },
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
expect(operationLog[0].params).toEqual({ q: 'test', limit: 10 });
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe('injectDryRunHttpClient', () => {
|
|
244
|
-
let operationLog;
|
|
245
|
-
let dryRunClient;
|
|
246
|
-
|
|
247
|
-
beforeEach(() => {
|
|
248
|
-
operationLog = [];
|
|
249
|
-
dryRunClient = createDryRunHttpClient(operationLog);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('should inject into primary API module', () => {
|
|
253
|
-
const integrationInstance = {
|
|
254
|
-
primary: {
|
|
255
|
-
api: {
|
|
256
|
-
_httpClient: { get: jest.fn() },
|
|
257
|
-
},
|
|
258
|
-
},
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
injectDryRunHttpClient(integrationInstance, dryRunClient);
|
|
262
|
-
|
|
263
|
-
expect(integrationInstance.primary.api._httpClient).toBe(dryRunClient);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test('should inject into target API module', () => {
|
|
267
|
-
const integrationInstance = {
|
|
268
|
-
target: {
|
|
269
|
-
api: {
|
|
270
|
-
_httpClient: { get: jest.fn() },
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
injectDryRunHttpClient(integrationInstance, dryRunClient);
|
|
276
|
-
|
|
277
|
-
expect(integrationInstance.target.api._httpClient).toBe(dryRunClient);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test('should inject into both primary and target', () => {
|
|
281
|
-
const integrationInstance = {
|
|
282
|
-
primary: {
|
|
283
|
-
api: { _httpClient: { get: jest.fn() } },
|
|
284
|
-
},
|
|
285
|
-
target: {
|
|
286
|
-
api: { _httpClient: { get: jest.fn() } },
|
|
287
|
-
},
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
injectDryRunHttpClient(integrationInstance, dryRunClient);
|
|
291
|
-
|
|
292
|
-
expect(integrationInstance.primary.api._httpClient).toBe(dryRunClient);
|
|
293
|
-
expect(integrationInstance.target.api._httpClient).toBe(dryRunClient);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test('should handle missing api modules gracefully', () => {
|
|
297
|
-
const integrationInstance = {
|
|
298
|
-
primary: {},
|
|
299
|
-
target: null,
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
expect(() => {
|
|
303
|
-
injectDryRunHttpClient(integrationInstance, dryRunClient);
|
|
304
|
-
}).not.toThrow();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test('should handle null integration instance', () => {
|
|
308
|
-
expect(() => {
|
|
309
|
-
injectDryRunHttpClient(null, dryRunClient);
|
|
310
|
-
}).not.toThrow();
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
});
|
|
@@ -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
|
-
});
|