@friggframework/admin-scripts 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.21b69ac.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/PR_517_REVIEW_TRACKER.md +56 -0
- package/index.js +12 -3
- package/package.json +6 -9
- package/src/application/__tests__/admin-frigg-commands.test.js +19 -19
- package/src/application/__tests__/admin-script-base.test.js +100 -84
- package/src/application/__tests__/script-runner.test.js +146 -16
- package/src/application/admin-frigg-commands.js +20 -32
- package/src/application/admin-script-base.js +20 -99
- package/src/application/script-runner.js +131 -135
- package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +168 -0
- package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +114 -0
- package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +201 -0
- package/src/application/use-cases/delete-schedule-use-case.js +108 -0
- package/src/application/use-cases/get-effective-schedule-use-case.js +78 -0
- package/src/application/use-cases/index.js +18 -0
- package/src/application/use-cases/upsert-schedule-use-case.js +127 -0
- package/src/builtins/__tests__/integration-health-check.test.js +67 -60
- package/src/builtins/__tests__/oauth-token-refresh.test.js +45 -37
- package/src/builtins/integration-health-check.js +23 -24
- package/src/builtins/oauth-token-refresh.js +19 -20
- package/src/infrastructure/__tests__/admin-auth-middleware.test.js +32 -95
- package/src/infrastructure/__tests__/admin-script-router.test.js +46 -47
- package/src/infrastructure/admin-auth-middleware.js +5 -43
- package/src/infrastructure/admin-script-router.js +38 -32
- 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/__tests__/schedule-management-use-case.test.js +0 -276
- package/src/application/dry-run-http-interceptor.js +0 -296
- package/src/application/dry-run-repository-wrapper.js +0 -261
- package/src/application/schedule-management-use-case.js +0 -230
|
@@ -54,7 +54,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
54
54
|
config: {
|
|
55
55
|
timeout: 900000, // 15 minutes
|
|
56
56
|
maxRetries: 0,
|
|
57
|
-
|
|
57
|
+
requireIntegrationInstance: true,
|
|
58
58
|
},
|
|
59
59
|
|
|
60
60
|
schedule: {
|
|
@@ -62,14 +62,13 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
62
62
|
cronExpression: 'cron(0 6 * * ? *)', // Daily at 6 AM UTC
|
|
63
63
|
},
|
|
64
64
|
|
|
65
|
+
// UI-specific overrides
|
|
65
66
|
display: {
|
|
66
|
-
label: 'Integration Health Check',
|
|
67
|
-
description: 'Check health and connectivity of integrations',
|
|
68
67
|
category: 'maintenance',
|
|
69
68
|
},
|
|
70
69
|
};
|
|
71
70
|
|
|
72
|
-
async execute(
|
|
71
|
+
async execute(params = {}) {
|
|
73
72
|
const {
|
|
74
73
|
integrationIds = null,
|
|
75
74
|
checkCredentials = true,
|
|
@@ -84,7 +83,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
84
83
|
results: []
|
|
85
84
|
};
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
this.context.log('info', 'Starting integration health check', {
|
|
88
87
|
checkCredentials,
|
|
89
88
|
checkConnectivity,
|
|
90
89
|
updateStatus,
|
|
@@ -95,17 +94,17 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
95
94
|
let integrations;
|
|
96
95
|
if (integrationIds && integrationIds.length > 0) {
|
|
97
96
|
integrations = await Promise.all(
|
|
98
|
-
integrationIds.map(id =>
|
|
97
|
+
integrationIds.map(id => this.context.findIntegrationById(id).catch(() => null))
|
|
99
98
|
);
|
|
100
99
|
integrations = integrations.filter(Boolean);
|
|
101
100
|
} else {
|
|
102
|
-
integrations = await this.getAllIntegrations(
|
|
101
|
+
integrations = await this.getAllIntegrations();
|
|
103
102
|
}
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
this.context.log('info', `Checking ${integrations.length} integrations`);
|
|
106
105
|
|
|
107
106
|
for (const integration of integrations) {
|
|
108
|
-
const result = await this.checkIntegration(
|
|
107
|
+
const result = await this.checkIntegration(integration, {
|
|
109
108
|
checkCredentials,
|
|
110
109
|
checkConnectivity
|
|
111
110
|
});
|
|
@@ -124,17 +123,17 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
124
123
|
if (updateStatus && result.status !== 'unknown') {
|
|
125
124
|
try {
|
|
126
125
|
const newStatus = result.status === 'healthy' ? 'ACTIVE' : 'ERROR';
|
|
127
|
-
await
|
|
128
|
-
|
|
126
|
+
await this.context.updateIntegrationStatus(integration.id, newStatus);
|
|
127
|
+
this.context.log('info', `Updated status for ${integration.id} to ${newStatus}`);
|
|
129
128
|
} catch (error) {
|
|
130
|
-
|
|
129
|
+
this.context.log('warn', `Failed to update status for ${integration.id}`, {
|
|
131
130
|
error: error.message
|
|
132
131
|
});
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
|
|
136
|
+
this.context.log('info', 'Health check completed', {
|
|
138
137
|
healthy: summary.healthy,
|
|
139
138
|
unhealthy: summary.unhealthy,
|
|
140
139
|
unknown: summary.unknown
|
|
@@ -143,19 +142,19 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
143
142
|
return summary;
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
async getAllIntegrations(
|
|
147
|
-
return
|
|
145
|
+
async getAllIntegrations() {
|
|
146
|
+
return this.context.listIntegrations({});
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
async checkIntegration(
|
|
149
|
+
async checkIntegration(integration, options) {
|
|
151
150
|
const { checkCredentials, checkConnectivity } = options;
|
|
152
151
|
const result = this._createCheckResult(integration);
|
|
153
152
|
|
|
154
153
|
try {
|
|
155
|
-
await this._runChecks(
|
|
154
|
+
await this._runChecks(integration, result, { checkCredentials, checkConnectivity });
|
|
156
155
|
this._determineOverallStatus(result);
|
|
157
156
|
} catch (error) {
|
|
158
|
-
this._handleCheckError(
|
|
157
|
+
this._handleCheckError(integration, result, error);
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
return result;
|
|
@@ -179,7 +178,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
179
178
|
* Run all requested checks
|
|
180
179
|
* @private
|
|
181
180
|
*/
|
|
182
|
-
async _runChecks(
|
|
181
|
+
async _runChecks(integration, result, options) {
|
|
183
182
|
const { checkCredentials, checkConnectivity } = options;
|
|
184
183
|
|
|
185
184
|
if (checkCredentials) {
|
|
@@ -187,7 +186,7 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
if (checkConnectivity) {
|
|
190
|
-
this._addCheckResult(result, 'connectivity', await this.checkApiConnectivity(
|
|
189
|
+
this._addCheckResult(result, 'connectivity', await this.checkApiConnectivity(integration));
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
|
|
@@ -214,8 +213,8 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
214
213
|
* Handle check error and update result
|
|
215
214
|
* @private
|
|
216
215
|
*/
|
|
217
|
-
_handleCheckError(
|
|
218
|
-
|
|
216
|
+
_handleCheckError(integration, result, error) {
|
|
217
|
+
this.context.log('error', `Error checking integration ${integration.id}`, {
|
|
219
218
|
error: error.message
|
|
220
219
|
});
|
|
221
220
|
result.status = 'unknown';
|
|
@@ -246,12 +245,12 @@ class IntegrationHealthCheckScript extends AdminScriptBase {
|
|
|
246
245
|
return result;
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
async checkApiConnectivity(
|
|
248
|
+
async checkApiConnectivity(integration) {
|
|
250
249
|
const result = { valid: true, issue: null, responseTime: null };
|
|
251
250
|
|
|
252
251
|
try {
|
|
253
252
|
const startTime = Date.now();
|
|
254
|
-
const instance = await
|
|
253
|
+
const instance = await this.context.instantiate(integration.id);
|
|
255
254
|
|
|
256
255
|
// Try to make a simple API call
|
|
257
256
|
if (instance.primary?.api?.getAuthenticationInfo) {
|
|
@@ -47,17 +47,16 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
47
47
|
config: {
|
|
48
48
|
timeout: 600000, // 10 minutes
|
|
49
49
|
maxRetries: 1,
|
|
50
|
-
|
|
50
|
+
requireIntegrationInstance: true, // Needs to call external APIs
|
|
51
51
|
},
|
|
52
52
|
|
|
53
|
+
// UI-specific overrides
|
|
53
54
|
display: {
|
|
54
|
-
label: 'OAuth Token Refresh',
|
|
55
|
-
description: 'Refresh OAuth tokens before they expire',
|
|
56
55
|
category: 'maintenance',
|
|
57
56
|
},
|
|
58
57
|
};
|
|
59
58
|
|
|
60
|
-
async execute(
|
|
59
|
+
async execute(params = {}) {
|
|
61
60
|
const {
|
|
62
61
|
integrationIds = null,
|
|
63
62
|
expiryThresholdHours = 24,
|
|
@@ -71,7 +70,7 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
71
70
|
details: []
|
|
72
71
|
};
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
this.context.log('info', 'Starting OAuth token refresh', {
|
|
75
74
|
expiryThresholdHours,
|
|
76
75
|
dryRun,
|
|
77
76
|
specificIds: integrationIds?.length || 'all'
|
|
@@ -81,19 +80,19 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
81
80
|
let integrations;
|
|
82
81
|
if (integrationIds && integrationIds.length > 0) {
|
|
83
82
|
integrations = await Promise.all(
|
|
84
|
-
integrationIds.map(id =>
|
|
83
|
+
integrationIds.map(id => this.context.findIntegrationById(id).catch(() => null))
|
|
85
84
|
);
|
|
86
85
|
integrations = integrations.filter(Boolean);
|
|
87
86
|
} else {
|
|
88
87
|
// Get all integrations (this would need to be paginated for large deployments)
|
|
89
|
-
integrations = await this.getAllIntegrations(
|
|
88
|
+
integrations = await this.getAllIntegrations();
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
this.context.log('info', `Found ${integrations.length} integrations to check`);
|
|
93
92
|
|
|
94
93
|
for (const integration of integrations) {
|
|
95
94
|
try {
|
|
96
|
-
const detail = await this.processIntegration(
|
|
95
|
+
const detail = await this.processIntegration(integration, {
|
|
97
96
|
expiryThresholdHours,
|
|
98
97
|
dryRun
|
|
99
98
|
});
|
|
@@ -108,7 +107,7 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
108
107
|
results.failed++;
|
|
109
108
|
}
|
|
110
109
|
} catch (error) {
|
|
111
|
-
|
|
110
|
+
this.context.log('error', `Error processing integration ${integration.id}`, {
|
|
112
111
|
error: error.message
|
|
113
112
|
});
|
|
114
113
|
results.failed++;
|
|
@@ -120,7 +119,7 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
120
119
|
}
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
this.context.log('info', 'OAuth token refresh completed', {
|
|
124
123
|
refreshed: results.refreshed,
|
|
125
124
|
failed: results.failed,
|
|
126
125
|
skipped: results.skipped
|
|
@@ -129,13 +128,13 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
129
128
|
return results;
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
async getAllIntegrations(
|
|
131
|
+
async getAllIntegrations() {
|
|
133
132
|
// This is a simplified implementation
|
|
134
133
|
// In production, would need pagination for large datasets
|
|
135
|
-
return
|
|
134
|
+
return this.context.listIntegrations({});
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
async processIntegration(
|
|
137
|
+
async processIntegration(integration, options) {
|
|
139
138
|
const { expiryThresholdHours, dryRun } = options;
|
|
140
139
|
|
|
141
140
|
// Check prerequisites
|
|
@@ -146,12 +145,12 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
146
145
|
|
|
147
146
|
// Handle dry run
|
|
148
147
|
if (dryRun) {
|
|
149
|
-
|
|
148
|
+
this.context.log('info', `[DRY RUN] Would refresh token for ${integration.id}`);
|
|
150
149
|
return this._createResult(integration.id, 'skipped', 'Dry run - would have refreshed');
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
// Perform refresh
|
|
154
|
-
return this._performTokenRefresh(
|
|
153
|
+
return this._performTokenRefresh(integration);
|
|
155
154
|
}
|
|
156
155
|
|
|
157
156
|
/**
|
|
@@ -183,18 +182,18 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
183
182
|
* Perform the actual token refresh
|
|
184
183
|
* @private
|
|
185
184
|
*/
|
|
186
|
-
async _performTokenRefresh(
|
|
185
|
+
async _performTokenRefresh(integration) {
|
|
187
186
|
const expiresAt = integration.config?.credentials?.expires_at;
|
|
188
187
|
|
|
189
188
|
try {
|
|
190
|
-
const instance = await
|
|
189
|
+
const instance = await this.context.instantiate(integration.id);
|
|
191
190
|
|
|
192
191
|
if (!instance.primary?.api?.refreshAccessToken) {
|
|
193
192
|
return this._createResult(integration.id, 'skipped', 'API does not support token refresh');
|
|
194
193
|
}
|
|
195
194
|
|
|
196
195
|
await instance.primary.api.refreshAccessToken();
|
|
197
|
-
|
|
196
|
+
this.context.log('info', `Refreshed token for integration ${integration.id}`);
|
|
198
197
|
|
|
199
198
|
return {
|
|
200
199
|
integrationId: integration.id,
|
|
@@ -202,7 +201,7 @@ class OAuthTokenRefreshScript extends AdminScriptBase {
|
|
|
202
201
|
previousExpiry: expiresAt
|
|
203
202
|
};
|
|
204
203
|
} catch (error) {
|
|
205
|
-
|
|
204
|
+
this.context.log('error', `Failed to refresh token for ${integration.id}`, {
|
|
206
205
|
error: error.message
|
|
207
206
|
});
|
|
208
207
|
return this._createResult(integration.id, 'failed', error.message);
|
|
@@ -1,22 +1,17 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { validateAdminApiKey } = require('../admin-auth-middleware');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
jest.mock('@friggframework/core/application/commands/admin-script-commands', () => ({
|
|
5
|
-
createAdminScriptCommands: jest.fn(),
|
|
6
|
-
}));
|
|
7
|
-
|
|
8
|
-
const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
|
|
9
|
-
|
|
10
|
-
describe('adminAuthMiddleware', () => {
|
|
3
|
+
describe('validateAdminApiKey', () => {
|
|
11
4
|
let mockReq;
|
|
12
5
|
let mockRes;
|
|
13
6
|
let mockNext;
|
|
14
|
-
let
|
|
7
|
+
let originalEnv;
|
|
15
8
|
|
|
16
9
|
beforeEach(() => {
|
|
10
|
+
originalEnv = process.env.ADMIN_API_KEY;
|
|
11
|
+
process.env.ADMIN_API_KEY = 'test-admin-key-123';
|
|
12
|
+
|
|
17
13
|
mockReq = {
|
|
18
14
|
headers: {},
|
|
19
|
-
ip: '127.0.0.1',
|
|
20
15
|
};
|
|
21
16
|
|
|
22
17
|
mockRes = {
|
|
@@ -25,124 +20,66 @@ describe('adminAuthMiddleware', () => {
|
|
|
25
20
|
};
|
|
26
21
|
|
|
27
22
|
mockNext = jest.fn();
|
|
28
|
-
|
|
29
|
-
mockCommands = {
|
|
30
|
-
validateAdminApiKey: jest.fn(),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
createAdminScriptCommands.mockReturnValue(mockCommands);
|
|
34
23
|
});
|
|
35
24
|
|
|
36
25
|
afterEach(() => {
|
|
26
|
+
if (originalEnv) {
|
|
27
|
+
process.env.ADMIN_API_KEY = originalEnv;
|
|
28
|
+
} else {
|
|
29
|
+
delete process.env.ADMIN_API_KEY;
|
|
30
|
+
}
|
|
37
31
|
jest.clearAllMocks();
|
|
38
32
|
});
|
|
39
33
|
|
|
40
|
-
describe('
|
|
41
|
-
it('should reject
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
expect(mockRes.status).toHaveBeenCalledWith(401);
|
|
45
|
-
expect(mockRes.json).toHaveBeenCalledWith({
|
|
46
|
-
error: 'Missing or invalid Authorization header',
|
|
47
|
-
code: 'MISSING_AUTH',
|
|
48
|
-
});
|
|
49
|
-
expect(mockNext).not.toHaveBeenCalled();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should reject request with invalid Authorization format', async () => {
|
|
53
|
-
mockReq.headers.authorization = 'InvalidFormat key123';
|
|
34
|
+
describe('Environment configuration', () => {
|
|
35
|
+
it('should reject when ADMIN_API_KEY not configured', () => {
|
|
36
|
+
delete process.env.ADMIN_API_KEY;
|
|
54
37
|
|
|
55
|
-
|
|
38
|
+
validateAdminApiKey(mockReq, mockRes, mockNext);
|
|
56
39
|
|
|
57
40
|
expect(mockRes.status).toHaveBeenCalledWith(401);
|
|
58
41
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
59
|
-
error: '
|
|
60
|
-
|
|
42
|
+
error: 'Unauthorized',
|
|
43
|
+
message: 'Admin API key not configured',
|
|
61
44
|
});
|
|
62
45
|
expect(mockNext).not.toHaveBeenCalled();
|
|
63
46
|
});
|
|
64
47
|
});
|
|
65
48
|
|
|
66
|
-
describe('
|
|
67
|
-
it('should reject request
|
|
68
|
-
mockReq
|
|
69
|
-
mockCommands.validateAdminApiKey.mockResolvedValue({
|
|
70
|
-
error: 401,
|
|
71
|
-
reason: 'Invalid API key',
|
|
72
|
-
code: 'INVALID_API_KEY',
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
await adminAuthMiddleware(mockReq, mockRes, mockNext);
|
|
49
|
+
describe('Header validation', () => {
|
|
50
|
+
it('should reject request without x-frigg-admin-api-key header', () => {
|
|
51
|
+
validateAdminApiKey(mockReq, mockRes, mockNext);
|
|
76
52
|
|
|
77
|
-
expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('invalid-key');
|
|
78
53
|
expect(mockRes.status).toHaveBeenCalledWith(401);
|
|
79
54
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
80
|
-
error: '
|
|
81
|
-
|
|
55
|
+
error: 'Unauthorized',
|
|
56
|
+
message: 'x-frigg-admin-api-key header required',
|
|
82
57
|
});
|
|
83
58
|
expect(mockNext).not.toHaveBeenCalled();
|
|
84
59
|
});
|
|
60
|
+
});
|
|
85
61
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
error: 401,
|
|
90
|
-
reason: 'API key has expired',
|
|
91
|
-
code: 'EXPIRED_API_KEY',
|
|
92
|
-
});
|
|
62
|
+
describe('API key validation', () => {
|
|
63
|
+
it('should reject request with invalid API key', () => {
|
|
64
|
+
mockReq.headers['x-frigg-admin-api-key'] = 'invalid-key';
|
|
93
65
|
|
|
94
|
-
|
|
66
|
+
validateAdminApiKey(mockReq, mockRes, mockNext);
|
|
95
67
|
|
|
96
|
-
expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith('expired-key');
|
|
97
68
|
expect(mockRes.status).toHaveBeenCalledWith(401);
|
|
98
69
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
99
|
-
error: '
|
|
100
|
-
|
|
70
|
+
error: 'Unauthorized',
|
|
71
|
+
message: 'Invalid admin API key',
|
|
101
72
|
});
|
|
102
73
|
expect(mockNext).not.toHaveBeenCalled();
|
|
103
74
|
});
|
|
104
75
|
|
|
105
|
-
it('should accept request with valid API key',
|
|
106
|
-
|
|
107
|
-
mockReq.headers.authorization = `Bearer ${validKey}`;
|
|
108
|
-
mockCommands.validateAdminApiKey.mockResolvedValue({
|
|
109
|
-
valid: true,
|
|
110
|
-
apiKey: {
|
|
111
|
-
id: 'key-id-1',
|
|
112
|
-
name: 'test-key',
|
|
113
|
-
keyLast4: 'e123',
|
|
114
|
-
},
|
|
115
|
-
});
|
|
76
|
+
it('should accept request with valid API key', () => {
|
|
77
|
+
mockReq.headers['x-frigg-admin-api-key'] = 'test-admin-key-123';
|
|
116
78
|
|
|
117
|
-
|
|
79
|
+
validateAdminApiKey(mockReq, mockRes, mockNext);
|
|
118
80
|
|
|
119
|
-
expect(mockCommands.validateAdminApiKey).toHaveBeenCalledWith(validKey);
|
|
120
|
-
expect(mockReq.adminApiKey).toBeDefined();
|
|
121
|
-
expect(mockReq.adminApiKey.name).toBe('test-key');
|
|
122
|
-
expect(mockReq.adminAudit).toBeDefined();
|
|
123
|
-
expect(mockReq.adminAudit.apiKeyName).toBe('test-key');
|
|
124
|
-
expect(mockReq.adminAudit.apiKeyLast4).toBe('e123');
|
|
125
|
-
expect(mockReq.adminAudit.ipAddress).toBe('127.0.0.1');
|
|
126
81
|
expect(mockNext).toHaveBeenCalled();
|
|
127
82
|
expect(mockRes.status).not.toHaveBeenCalled();
|
|
128
83
|
});
|
|
129
84
|
});
|
|
130
|
-
|
|
131
|
-
describe('Error handling', () => {
|
|
132
|
-
it('should handle validation errors gracefully', async () => {
|
|
133
|
-
mockReq.headers.authorization = 'Bearer some-key';
|
|
134
|
-
mockCommands.validateAdminApiKey.mockRejectedValue(
|
|
135
|
-
new Error('Database error')
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
await adminAuthMiddleware(mockReq, mockRes, mockNext);
|
|
139
|
-
|
|
140
|
-
expect(mockRes.status).toHaveBeenCalledWith(500);
|
|
141
|
-
expect(mockRes.json).toHaveBeenCalledWith({
|
|
142
|
-
error: 'Authentication failed',
|
|
143
|
-
code: 'AUTH_ERROR',
|
|
144
|
-
});
|
|
145
|
-
expect(mockNext).not.toHaveBeenCalled();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
85
|
});
|