@friggframework/admin-scripts 2.0.0--canary.517.300ded3.0 → 2.0.0--canary.522.cbd3d5a.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 CHANGED
@@ -12,7 +12,7 @@ const { AdminFriggCommands, createAdminFriggCommands } = require('./src/applicat
12
12
  const { ScriptRunner, createScriptRunner } = require('./src/application/script-runner');
13
13
 
14
14
  // Infrastructure
15
- const { validateAdminApiKey } = require('./src/infrastructure/admin-auth-middleware');
15
+ const { adminAuthMiddleware } = require('./src/infrastructure/admin-auth-middleware');
16
16
  const { router, app, handler: routerHandler } = require('./src/infrastructure/admin-script-router');
17
17
  const { handler: executorHandler } = require('./src/infrastructure/script-executor-handler');
18
18
 
@@ -45,7 +45,7 @@ module.exports = {
45
45
  createScriptRunner,
46
46
 
47
47
  // Infrastructure layer
48
- validateAdminApiKey,
48
+ adminAuthMiddleware,
49
49
  router,
50
50
  app,
51
51
  routerHandler,
package/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.300ded3.0",
4
+ "version": "2.0.0--canary.522.cbd3d5a.0",
5
5
  "description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
6
6
  "dependencies": {
7
7
  "@aws-sdk/client-scheduler": "^3.588.0",
8
- "@friggframework/core": "2.0.0--canary.517.300ded3.0",
8
+ "@friggframework/core": "2.0.0--canary.522.cbd3d5a.0",
9
9
  "bcryptjs": "^2.4.3",
10
10
  "express": "^4.18.2",
11
11
  "lodash": "4.17.21",
12
+ "mongoose": "6.11.6",
12
13
  "serverless-http": "^3.2.0",
13
14
  "uuid": "^9.0.1"
14
15
  },
15
16
  "devDependencies": {
16
- "@friggframework/eslint-config": "2.0.0--canary.517.300ded3.0",
17
- "@friggframework/prettier-config": "2.0.0--canary.517.300ded3.0",
18
- "@friggframework/test": "2.0.0--canary.517.300ded3.0",
17
+ "@friggframework/eslint-config": "2.0.0--canary.522.cbd3d5a.0",
18
+ "@friggframework/prettier-config": "2.0.0--canary.522.cbd3d5a.0",
19
+ "@friggframework/test": "2.0.0--canary.522.cbd3d5a.0",
20
+ "chai": "^4.3.6",
19
21
  "eslint": "^8.22.0",
20
22
  "jest": "^29.7.0",
21
23
  "prettier": "^2.7.1",
@@ -47,5 +49,5 @@
47
49
  "maintenance",
48
50
  "operations"
49
51
  ],
50
- "gitHead": "300ded3ac35558075a081104b4b362b85cf0756f"
52
+ "gitHead": "cbd3d5a81315519842f308fc0bd786a3f8786b79"
51
53
  }
@@ -5,7 +5,7 @@ jest.mock('@friggframework/core/integrations/repositories/integration-repository
5
5
  jest.mock('@friggframework/core/user/repositories/user-repository-factory');
6
6
  jest.mock('@friggframework/core/modules/repositories/module-repository-factory');
7
7
  jest.mock('@friggframework/core/credential/repositories/credential-repository-factory');
8
- jest.mock('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
8
+ jest.mock('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
9
9
  jest.mock('@friggframework/core/queues');
10
10
 
11
11
  describe('AdminFriggCommands', () => {
@@ -13,7 +13,7 @@ describe('AdminFriggCommands', () => {
13
13
  let mockUserRepo;
14
14
  let mockModuleRepo;
15
15
  let mockCredentialRepo;
16
- let mockAdminProcessRepo;
16
+ let mockScriptExecutionRepo;
17
17
  let mockQueuerUtil;
18
18
 
19
19
  beforeEach(() => {
@@ -46,8 +46,8 @@ describe('AdminFriggCommands', () => {
46
46
  updateCredential: jest.fn(),
47
47
  };
48
48
 
49
- mockAdminProcessRepo = {
50
- appendProcessLog: jest.fn().mockResolvedValue(undefined),
49
+ mockScriptExecutionRepo = {
50
+ appendExecutionLog: jest.fn().mockResolvedValue(undefined),
51
51
  };
52
52
 
53
53
  mockQueuerUtil = {
@@ -60,14 +60,14 @@ describe('AdminFriggCommands', () => {
60
60
  const { createUserRepository } = require('@friggframework/core/user/repositories/user-repository-factory');
61
61
  const { createModuleRepository } = require('@friggframework/core/modules/repositories/module-repository-factory');
62
62
  const { createCredentialRepository } = require('@friggframework/core/credential/repositories/credential-repository-factory');
63
- const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
63
+ const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
64
64
  const { QueuerUtil } = require('@friggframework/core/queues');
65
65
 
66
66
  createIntegrationRepository.mockReturnValue(mockIntegrationRepo);
67
67
  createUserRepository.mockReturnValue(mockUserRepo);
68
68
  createModuleRepository.mockReturnValue(mockModuleRepo);
69
69
  createCredentialRepository.mockReturnValue(mockCredentialRepo);
70
- createAdminProcessRepository.mockReturnValue(mockAdminProcessRepo);
70
+ createScriptExecutionRepository.mockReturnValue(mockScriptExecutionRepo);
71
71
 
72
72
  // Mock QueuerUtil methods
73
73
  QueuerUtil.send = mockQueuerUtil.send;
@@ -158,16 +158,16 @@ describe('AdminFriggCommands', () => {
158
158
  expect(repo).toBe(mockCredentialRepo);
159
159
  });
160
160
 
161
- it('creates adminProcessRepository on first access', () => {
161
+ it('creates scriptExecutionRepository on first access', () => {
162
162
  const commands = new AdminFriggCommands();
163
- const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
163
+ const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
164
164
 
165
- expect(createAdminProcessRepository).not.toHaveBeenCalled();
165
+ expect(createScriptExecutionRepository).not.toHaveBeenCalled();
166
166
 
167
- const repo = commands.adminProcessRepository;
167
+ const repo = commands.scriptExecutionRepository;
168
168
 
169
- expect(createAdminProcessRepository).toHaveBeenCalledTimes(1);
170
- expect(repo).toBe(mockAdminProcessRepo);
169
+ expect(createScriptExecutionRepository).toHaveBeenCalledTimes(1);
170
+ expect(repo).toBe(mockScriptExecutionRepo);
171
171
  });
172
172
  });
173
173
 
@@ -551,15 +551,15 @@ describe('AdminFriggCommands', () => {
551
551
  it('log() persists if executionId set', async () => {
552
552
  const commands = new AdminFriggCommands({ executionId: 'exec_123' });
553
553
  // Force repository creation
554
- commands.adminProcessRepository;
554
+ commands.scriptExecutionRepository;
555
555
 
556
556
  commands.log('warn', 'Warning message', { detail: 'xyz' });
557
557
 
558
558
  // Give async operation a chance to execute
559
559
  await new Promise(resolve => setImmediate(resolve));
560
560
 
561
- expect(mockAdminProcessRepo.appendProcessLog).toHaveBeenCalled();
562
- const callArgs = mockAdminProcessRepo.appendProcessLog.mock.calls[0];
561
+ expect(mockScriptExecutionRepo.appendExecutionLog).toHaveBeenCalled();
562
+ const callArgs = mockScriptExecutionRepo.appendExecutionLog.mock.calls[0];
563
563
  expect(callArgs[0]).toBe('exec_123');
564
564
  expect(callArgs[1].level).toBe('warn');
565
565
  expect(callArgs[1].message).toBe('Warning message');
@@ -572,14 +572,14 @@ describe('AdminFriggCommands', () => {
572
572
 
573
573
  await new Promise(resolve => setImmediate(resolve));
574
574
 
575
- expect(mockAdminProcessRepo.appendProcessLog).not.toHaveBeenCalled();
575
+ expect(mockScriptExecutionRepo.appendExecutionLog).not.toHaveBeenCalled();
576
576
  });
577
577
 
578
578
  it('log() handles persistence failure gracefully', async () => {
579
579
  const commands = new AdminFriggCommands({ executionId: 'exec_123' });
580
580
  // Force repository creation
581
- commands.adminProcessRepository;
582
- mockAdminProcessRepo.appendProcessLog.mockRejectedValue(new Error('DB Error'));
581
+ commands.scriptExecutionRepository;
582
+ mockScriptExecutionRepo.appendExecutionLog.mockRejectedValue(new Error('DB Error'));
583
583
 
584
584
  // Should not throw
585
585
  expect(() => commands.log('error', 'Test error')).not.toThrow();
@@ -0,0 +1,313 @@
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
+ });