@friggframework/core 2.0.0--canary.400.545e7a8.0 → 2.0.0--canary.404.e9d4980.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.
|
@@ -8,11 +8,6 @@ const schema = new mongoose.Schema({
|
|
|
8
8
|
// Add a static method to get active connections
|
|
9
9
|
schema.statics.getActiveConnections = async function () {
|
|
10
10
|
try {
|
|
11
|
-
// Return empty array if websockets are not configured
|
|
12
|
-
if (!process.env.WEBSOCKET_API_ENDPOINT) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
const connections = await this.find({}, 'connectionId');
|
|
17
12
|
return connections.map((conn) => ({
|
|
18
13
|
connectionId: conn.connectionId,
|
|
@@ -9,15 +9,14 @@ The Frigg service includes comprehensive healthcheck endpoints to monitor servic
|
|
|
9
9
|
### 1. Basic Health Check
|
|
10
10
|
**GET** `/health`
|
|
11
11
|
|
|
12
|
-
Simple health check endpoint that returns basic service information. No authentication required.
|
|
12
|
+
Simple health check endpoint that returns basic service information. No authentication required. This endpoint is rate-limited at the API Gateway level.
|
|
13
13
|
|
|
14
14
|
**Response:**
|
|
15
15
|
```json
|
|
16
16
|
{
|
|
17
17
|
"status": "ok",
|
|
18
18
|
"timestamp": "2024-01-10T12:00:00.000Z",
|
|
19
|
-
"service": "frigg-core-api"
|
|
20
|
-
"version": "1.0.0"
|
|
19
|
+
"service": "frigg-core-api"
|
|
21
20
|
}
|
|
22
21
|
```
|
|
23
22
|
|
|
@@ -29,22 +28,23 @@ Simple health check endpoint that returns basic service information. No authenti
|
|
|
29
28
|
|
|
30
29
|
Comprehensive health check that tests all service components and dependencies.
|
|
31
30
|
|
|
31
|
+
**Authentication Required:**
|
|
32
|
+
- Header: `x-api-key: YOUR_API_KEY`
|
|
33
|
+
- The API key must match the `HEALTH_API_KEY` environment variable
|
|
34
|
+
|
|
32
35
|
**Response:**
|
|
33
36
|
```json
|
|
34
37
|
{
|
|
35
38
|
"service": "frigg-core-api",
|
|
36
|
-
"status": "healthy", // "healthy"
|
|
39
|
+
"status": "healthy", // "healthy" or "unhealthy"
|
|
37
40
|
"timestamp": "2024-01-10T12:00:00.000Z",
|
|
38
|
-
"version": "1.0.0",
|
|
39
|
-
"uptime": 3600, // seconds
|
|
40
41
|
"checks": {
|
|
41
42
|
"database": {
|
|
42
43
|
"status": "healthy",
|
|
43
44
|
"state": "connected",
|
|
44
|
-
"type": "mongodb",
|
|
45
45
|
"responseTime": 5 // milliseconds
|
|
46
46
|
},
|
|
47
|
-
"
|
|
47
|
+
"externalApis": {
|
|
48
48
|
"github": {
|
|
49
49
|
"status": "healthy",
|
|
50
50
|
"statusCode": 200,
|
|
@@ -68,13 +68,6 @@ Comprehensive health check that tests all service components and dependencies.
|
|
|
68
68
|
"count": 5,
|
|
69
69
|
"available": ["integration1", "integration2", "..."]
|
|
70
70
|
}
|
|
71
|
-
},
|
|
72
|
-
"memory": {
|
|
73
|
-
"status": "healthy",
|
|
74
|
-
"rss": "150 MB",
|
|
75
|
-
"heapTotal": "100 MB",
|
|
76
|
-
"heapUsed": "80 MB",
|
|
77
|
-
"external": "20 MB"
|
|
78
71
|
}
|
|
79
72
|
},
|
|
80
73
|
"responseTime": 250 // total endpoint response time in milliseconds
|
|
@@ -82,14 +75,18 @@ Comprehensive health check that tests all service components and dependencies.
|
|
|
82
75
|
```
|
|
83
76
|
|
|
84
77
|
**Status Codes:**
|
|
85
|
-
- `200 OK` - Service is healthy
|
|
86
|
-
- `503 Service Unavailable` - Service is unhealthy
|
|
78
|
+
- `200 OK` - Service is healthy (all components operational)
|
|
79
|
+
- `503 Service Unavailable` - Service is unhealthy (any component failure)
|
|
80
|
+
- `401 Unauthorized` - Missing or invalid x-api-key header
|
|
87
81
|
|
|
88
82
|
### 3. Liveness Probe
|
|
89
83
|
**GET** `/health/live`
|
|
90
84
|
|
|
91
85
|
Kubernetes-style liveness probe. Returns whether the service process is alive.
|
|
92
86
|
|
|
87
|
+
**Authentication Required:**
|
|
88
|
+
- Header: `x-api-key: YOUR_API_KEY`
|
|
89
|
+
|
|
93
90
|
**Response:**
|
|
94
91
|
```json
|
|
95
92
|
{
|
|
@@ -106,6 +103,9 @@ Kubernetes-style liveness probe. Returns whether the service process is alive.
|
|
|
106
103
|
|
|
107
104
|
Kubernetes-style readiness probe. Returns whether the service is ready to receive traffic.
|
|
108
105
|
|
|
106
|
+
**Authentication Required:**
|
|
107
|
+
- Header: `x-api-key: YOUR_API_KEY`
|
|
108
|
+
|
|
109
109
|
**Response:**
|
|
110
110
|
```json
|
|
111
111
|
{
|
|
@@ -125,35 +125,32 @@ Kubernetes-style readiness probe. Returns whether the service is ready to receiv
|
|
|
125
125
|
## Health Status Definitions
|
|
126
126
|
|
|
127
127
|
- **healthy**: All components are functioning normally
|
|
128
|
-
- **
|
|
129
|
-
- **unhealthy**: Critical components are failing, service cannot function properly
|
|
128
|
+
- **unhealthy**: Any component is failing, service may not function properly
|
|
130
129
|
|
|
131
130
|
## Component Checks
|
|
132
131
|
|
|
133
132
|
### Database Connectivity
|
|
134
|
-
- Checks
|
|
135
|
-
- Performs ping test if connected
|
|
133
|
+
- Checks database connection state
|
|
134
|
+
- Performs ping test with 2-second timeout if connected
|
|
136
135
|
- Reports connection state and response time
|
|
136
|
+
- Database type is not exposed for security reasons
|
|
137
137
|
|
|
138
138
|
### External API Connectivity
|
|
139
139
|
- Tests connectivity to external services (GitHub, npm registry)
|
|
140
140
|
- Configurable timeout (default: 5 seconds)
|
|
141
141
|
- Reports reachability and response times
|
|
142
|
+
- Uses Promise.all for parallel checking
|
|
142
143
|
|
|
143
144
|
### Integration Status
|
|
144
145
|
- Verifies available modules and integrations are loaded
|
|
145
146
|
- Reports counts and lists of available components
|
|
146
147
|
|
|
147
|
-
### Memory Usage
|
|
148
|
-
- Reports current memory usage statistics
|
|
149
|
-
- Includes RSS, heap, and external memory metrics
|
|
150
|
-
|
|
151
148
|
## Usage Examples
|
|
152
149
|
|
|
153
150
|
### Monitoring Systems
|
|
154
151
|
Configure your monitoring system to poll `/health/detailed` every 30-60 seconds:
|
|
155
152
|
```bash
|
|
156
|
-
curl https://your-frigg-instance.com/health/detailed
|
|
153
|
+
curl -H "x-api-key: YOUR_API_KEY" https://your-frigg-instance.com/health/detailed
|
|
157
154
|
```
|
|
158
155
|
|
|
159
156
|
### Load Balancer Health Checks
|
|
@@ -168,6 +165,9 @@ livenessProbe:
|
|
|
168
165
|
httpGet:
|
|
169
166
|
path: /health/live
|
|
170
167
|
port: 8080
|
|
168
|
+
httpHeaders:
|
|
169
|
+
- name: x-api-key
|
|
170
|
+
value: YOUR_API_KEY
|
|
171
171
|
periodSeconds: 10
|
|
172
172
|
timeoutSeconds: 5
|
|
173
173
|
|
|
@@ -175,6 +175,9 @@ readinessProbe:
|
|
|
175
175
|
httpGet:
|
|
176
176
|
path: /health/ready
|
|
177
177
|
port: 8080
|
|
178
|
+
httpHeaders:
|
|
179
|
+
- name: x-api-key
|
|
180
|
+
value: YOUR_API_KEY
|
|
178
181
|
initialDelaySeconds: 30
|
|
179
182
|
periodSeconds: 10
|
|
180
183
|
```
|
|
@@ -192,20 +195,24 @@ const externalAPIs = [
|
|
|
192
195
|
```
|
|
193
196
|
|
|
194
197
|
### Adjusting Timeouts
|
|
195
|
-
The default timeout for external API checks is 5 seconds.
|
|
198
|
+
The default timeout for external API checks is 5 seconds. Database ping timeout is set to 2 seconds:
|
|
196
199
|
```javascript
|
|
197
200
|
const checkExternalAPI = (url, timeout = 5000) => {
|
|
198
201
|
// ...
|
|
199
202
|
};
|
|
203
|
+
|
|
204
|
+
await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
|
|
200
205
|
```
|
|
201
206
|
|
|
202
207
|
## Best Practices
|
|
203
208
|
|
|
204
|
-
1. **
|
|
205
|
-
2. **
|
|
206
|
-
3. **
|
|
207
|
-
4. **
|
|
208
|
-
5. **
|
|
209
|
+
1. **Authentication**: Basic `/health` endpoint requires no authentication, but detailed endpoints require `x-api-key` header
|
|
210
|
+
2. **Rate Limiting**: Configure rate limiting at the API Gateway level to prevent abuse
|
|
211
|
+
3. **Fast Response**: Health checks should respond quickly (< 1 second)
|
|
212
|
+
4. **Strict Status Codes**: Return 503 for any non-healthy state to ensure proper alerting
|
|
213
|
+
5. **Detailed Logging**: Failed health checks are logged for debugging
|
|
214
|
+
6. **Security**: No sensitive information (DB types, versions) exposed in responses
|
|
215
|
+
7. **Lambda Considerations**: Uptime and memory metrics not included as they're not relevant in serverless
|
|
209
216
|
|
|
210
217
|
## Troubleshooting
|
|
211
218
|
|
|
@@ -216,15 +223,18 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
216
223
|
|
|
217
224
|
### External API Failures
|
|
218
225
|
- May indicate network issues or external service downtime
|
|
219
|
-
- Service
|
|
220
|
-
|
|
221
|
-
### Memory Issues
|
|
222
|
-
- Monitor memory metrics over time
|
|
223
|
-
- Consider increasing container/instance memory limits if consistently high
|
|
226
|
+
- Service reports "unhealthy" status if any external API is unreachable
|
|
224
227
|
|
|
225
228
|
## Security Considerations
|
|
226
229
|
|
|
230
|
+
- Basic health endpoint requires no authentication for monitoring compatibility
|
|
231
|
+
- Detailed endpoints require `x-api-key` header authentication
|
|
227
232
|
- Health endpoints do not expose sensitive information
|
|
228
233
|
- Database connection strings and credentials are never included in responses
|
|
229
234
|
- External API checks use read-only endpoints
|
|
230
|
-
-
|
|
235
|
+
- Rate limiting should be configured at the API Gateway level
|
|
236
|
+
- Consider IP whitelisting for health endpoints in production
|
|
237
|
+
|
|
238
|
+
## Environment Variables
|
|
239
|
+
|
|
240
|
+
- `HEALTH_API_KEY`: Required API key for accessing detailed health endpoints
|
|
@@ -4,11 +4,28 @@ const https = require('https');
|
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const { moduleFactory, integrationFactory } = require('./../backend-utils');
|
|
6
6
|
const { createAppHandler } = require('./../app-handler-helpers');
|
|
7
|
-
const { version } = require('../../package.json');
|
|
8
7
|
|
|
9
8
|
const router = Router();
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
const validateApiKey = (req, res, next) => {
|
|
11
|
+
const apiKey = req.headers['x-api-key'];
|
|
12
|
+
|
|
13
|
+
if (req.path === '/health') {
|
|
14
|
+
return next();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!apiKey || apiKey !== process.env.HEALTH_API_KEY) {
|
|
18
|
+
return res.status(401).json({
|
|
19
|
+
status: 'error',
|
|
20
|
+
message: 'Unauthorized'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
next();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
router.use(validateApiKey);
|
|
28
|
+
|
|
12
29
|
const checkExternalAPI = (url, timeout = 5000) => {
|
|
13
30
|
return new Promise((resolve) => {
|
|
14
31
|
const protocol = url.startsWith('https:') ? https : http;
|
|
@@ -54,31 +71,25 @@ const checkExternalAPI = (url, timeout = 5000) => {
|
|
|
54
71
|
});
|
|
55
72
|
};
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
router.get('/health', async (req, res) => {
|
|
74
|
+
router.get('/health', async (_req, res) => {
|
|
59
75
|
const status = {
|
|
60
76
|
status: 'ok',
|
|
61
77
|
timestamp: new Date().toISOString(),
|
|
62
|
-
service: 'frigg-core-api'
|
|
63
|
-
version
|
|
78
|
+
service: 'frigg-core-api'
|
|
64
79
|
};
|
|
65
80
|
|
|
66
81
|
res.status(200).json(status);
|
|
67
82
|
});
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
router.get('/health/detailed', async (req, res) => {
|
|
84
|
+
router.get('/health/detailed', async (_req, res) => {
|
|
71
85
|
const startTime = Date.now();
|
|
72
86
|
const checks = {
|
|
73
87
|
service: 'frigg-core-api',
|
|
74
88
|
status: 'healthy',
|
|
75
89
|
timestamp: new Date().toISOString(),
|
|
76
|
-
version,
|
|
77
|
-
uptime: process.uptime(),
|
|
78
90
|
checks: {}
|
|
79
91
|
};
|
|
80
92
|
|
|
81
|
-
// Check database connectivity
|
|
82
93
|
try {
|
|
83
94
|
const dbState = mongoose.connection.readyState;
|
|
84
95
|
const dbStateMap = {
|
|
@@ -90,41 +101,45 @@ router.get('/health/detailed', async (req, res) => {
|
|
|
90
101
|
|
|
91
102
|
checks.checks.database = {
|
|
92
103
|
status: dbState === 1 ? 'healthy' : 'unhealthy',
|
|
93
|
-
state: dbStateMap[dbState]
|
|
94
|
-
type: 'mongodb'
|
|
104
|
+
state: dbStateMap[dbState]
|
|
95
105
|
};
|
|
96
106
|
|
|
97
|
-
// If connected, check database responsiveness
|
|
98
107
|
if (dbState === 1) {
|
|
99
108
|
const pingStart = Date.now();
|
|
100
|
-
await mongoose.connection.db.admin().ping();
|
|
109
|
+
await mongoose.connection.db.admin().ping({ maxTimeMS: 2000 });
|
|
101
110
|
checks.checks.database.responseTime = Date.now() - pingStart;
|
|
111
|
+
} else {
|
|
112
|
+
checks.status = 'unhealthy';
|
|
102
113
|
}
|
|
103
114
|
} catch (error) {
|
|
104
115
|
checks.checks.database = {
|
|
105
116
|
status: 'unhealthy',
|
|
106
|
-
error: error.message
|
|
107
|
-
type: 'mongodb'
|
|
117
|
+
error: error.message
|
|
108
118
|
};
|
|
109
|
-
checks.status = '
|
|
119
|
+
checks.status = 'unhealthy';
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
// Check external API connectivity (example endpoints)
|
|
113
122
|
const externalAPIs = [
|
|
114
123
|
{ name: 'github', url: 'https://api.github.com/status' },
|
|
115
124
|
{ name: 'npm', url: 'https://registry.npmjs.org' }
|
|
116
125
|
];
|
|
117
126
|
|
|
118
|
-
checks.checks.
|
|
127
|
+
checks.checks.externalApis = {};
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
const apiChecks = await Promise.all(
|
|
130
|
+
externalAPIs.map(api =>
|
|
131
|
+
checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
apiChecks.forEach(result => {
|
|
136
|
+
const { name, ...checkResult } = result;
|
|
137
|
+
checks.checks.externalApis[name] = checkResult;
|
|
138
|
+
if (!checkResult.reachable) {
|
|
139
|
+
checks.status = 'unhealthy';
|
|
124
140
|
}
|
|
125
|
-
}
|
|
141
|
+
});
|
|
126
142
|
|
|
127
|
-
// Check available integrations
|
|
128
143
|
try {
|
|
129
144
|
const availableModules = moduleFactory.getAll();
|
|
130
145
|
const availableIntegrations = integrationFactory.getAll();
|
|
@@ -145,50 +160,33 @@ router.get('/health/detailed', async (req, res) => {
|
|
|
145
160
|
status: 'unhealthy',
|
|
146
161
|
error: error.message
|
|
147
162
|
};
|
|
148
|
-
checks.status = '
|
|
163
|
+
checks.status = 'unhealthy';
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
// Memory usage
|
|
152
|
-
const memoryUsage = process.memoryUsage();
|
|
153
|
-
checks.checks.memory = {
|
|
154
|
-
status: 'healthy',
|
|
155
|
-
rss: Math.round(memoryUsage.rss / 1024 / 1024) + ' MB',
|
|
156
|
-
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + ' MB',
|
|
157
|
-
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + ' MB',
|
|
158
|
-
external: Math.round(memoryUsage.external / 1024 / 1024) + ' MB'
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Overall response time
|
|
162
166
|
checks.responseTime = Date.now() - startTime;
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
const statusCode = checks.status === 'healthy' ? 200 :
|
|
166
|
-
checks.status === 'degraded' ? 200 : 503;
|
|
168
|
+
const statusCode = checks.status === 'healthy' ? 200 : 503;
|
|
167
169
|
|
|
168
170
|
res.status(statusCode).json(checks);
|
|
169
171
|
});
|
|
170
172
|
|
|
171
|
-
|
|
172
|
-
router.get('/health/live', (req, res) => {
|
|
173
|
+
router.get('/health/live', (_req, res) => {
|
|
173
174
|
res.status(200).json({
|
|
174
175
|
status: 'alive',
|
|
175
176
|
timestamp: new Date().toISOString()
|
|
176
177
|
});
|
|
177
178
|
});
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
router.get('/health/ready', async (req, res) => {
|
|
180
|
+
router.get('/health/ready', async (_req, res) => {
|
|
181
181
|
const checks = {
|
|
182
182
|
ready: true,
|
|
183
183
|
timestamp: new Date().toISOString(),
|
|
184
184
|
checks: {}
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
-
// Check database is connected
|
|
188
187
|
const dbState = mongoose.connection.readyState;
|
|
189
188
|
checks.checks.database = dbState === 1;
|
|
190
189
|
|
|
191
|
-
// Check critical services are loaded
|
|
192
190
|
try {
|
|
193
191
|
const modules = moduleFactory.getAll();
|
|
194
192
|
checks.checks.modules = Object.keys(modules).length > 0;
|
|
@@ -196,7 +194,6 @@ router.get('/health/ready', async (req, res) => {
|
|
|
196
194
|
checks.checks.modules = false;
|
|
197
195
|
}
|
|
198
196
|
|
|
199
|
-
// Determine overall readiness
|
|
200
197
|
checks.ready = checks.checks.database && checks.checks.modules;
|
|
201
198
|
|
|
202
199
|
const statusCode = checks.ready ? 200 : 503;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
const express = require('express');
|
|
3
|
-
const { router } = require('./health');
|
|
4
|
-
const mongoose = require('mongoose');
|
|
1
|
+
process.env.HEALTH_API_KEY = 'test-api-key';
|
|
5
2
|
|
|
6
|
-
// Mock mongoose connection
|
|
7
3
|
jest.mock('mongoose', () => ({
|
|
4
|
+
set: jest.fn(),
|
|
8
5
|
connection: {
|
|
9
6
|
readyState: 1,
|
|
10
7
|
db: {
|
|
@@ -15,7 +12,6 @@ jest.mock('mongoose', () => ({
|
|
|
15
12
|
}
|
|
16
13
|
}));
|
|
17
14
|
|
|
18
|
-
// Mock backend-utils
|
|
19
15
|
jest.mock('./../backend-utils', () => ({
|
|
20
16
|
moduleFactory: {
|
|
21
17
|
getAll: () => ({
|
|
@@ -31,106 +27,183 @@ jest.mock('./../backend-utils', () => ({
|
|
|
31
27
|
}
|
|
32
28
|
}));
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
jest.mock('./../app-handler-helpers', () => ({
|
|
31
|
+
createAppHandler: jest.fn((name, router) => ({ name, router }))
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const { router } = require('./health');
|
|
35
|
+
const mongoose = require('mongoose');
|
|
36
36
|
|
|
37
|
+
const mockRequest = (path, headers = {}) => ({
|
|
38
|
+
path,
|
|
39
|
+
headers
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const mockResponse = () => {
|
|
43
|
+
const res = {};
|
|
44
|
+
res.status = jest.fn().mockReturnValue(res);
|
|
45
|
+
res.json = jest.fn().mockReturnValue(res);
|
|
46
|
+
return res;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe('Health Check Endpoints', () => {
|
|
37
50
|
beforeEach(() => {
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
mongoose.connection.readyState = 1;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('Middleware - validateApiKey', () => {
|
|
55
|
+
it('should allow access to /health without authentication', async () => {
|
|
56
|
+
expect(true).toBe(true);
|
|
57
|
+
});
|
|
40
58
|
});
|
|
41
59
|
|
|
42
60
|
describe('GET /health', () => {
|
|
43
|
-
it('should return
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
it('should return basic health status', async () => {
|
|
62
|
+
const req = mockRequest('/health');
|
|
63
|
+
const res = mockResponse();
|
|
64
|
+
|
|
65
|
+
const routeHandler = router.stack.find(layer =>
|
|
66
|
+
layer.route && layer.route.path === '/health'
|
|
67
|
+
).route.stack[0].handle;
|
|
68
|
+
|
|
69
|
+
await routeHandler(req, res);
|
|
70
|
+
|
|
71
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
72
|
+
expect(res.json).toHaveBeenCalledWith({
|
|
73
|
+
status: 'ok',
|
|
74
|
+
timestamp: expect.any(String),
|
|
75
|
+
service: 'frigg-core-api'
|
|
76
|
+
});
|
|
52
77
|
});
|
|
53
78
|
});
|
|
54
79
|
|
|
55
80
|
describe('GET /health/detailed', () => {
|
|
56
|
-
it('should return
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
it('should return detailed health status when healthy', async () => {
|
|
82
|
+
const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
|
|
83
|
+
const res = mockResponse();
|
|
84
|
+
|
|
85
|
+
const originalPromiseAll = Promise.all;
|
|
86
|
+
Promise.all = jest.fn().mockResolvedValue([
|
|
87
|
+
{ name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
|
|
88
|
+
{ name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const routeHandler = router.stack.find(layer =>
|
|
92
|
+
layer.route && layer.route.path === '/health/detailed'
|
|
93
|
+
).route.stack[0].handle;
|
|
94
|
+
|
|
95
|
+
await routeHandler(req, res);
|
|
96
|
+
|
|
97
|
+
Promise.all = originalPromiseAll;
|
|
98
|
+
|
|
99
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
100
|
+
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
|
101
|
+
status: 'healthy',
|
|
102
|
+
service: 'frigg-core-api',
|
|
103
|
+
timestamp: expect.any(String),
|
|
104
|
+
checks: expect.objectContaining({
|
|
105
|
+
database: expect.objectContaining({
|
|
106
|
+
status: 'healthy',
|
|
107
|
+
state: 'connected'
|
|
108
|
+
}),
|
|
109
|
+
integrations: expect.objectContaining({
|
|
110
|
+
status: 'healthy'
|
|
111
|
+
})
|
|
112
|
+
}),
|
|
113
|
+
responseTime: expect.any(Number)
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const response = res.json.mock.calls[0][0];
|
|
117
|
+
expect(response).not.toHaveProperty('version');
|
|
118
|
+
expect(response).not.toHaveProperty('uptime');
|
|
119
|
+
expect(response.checks).not.toHaveProperty('memory');
|
|
120
|
+
expect(response.checks.database).not.toHaveProperty('type');
|
|
69
121
|
});
|
|
70
122
|
|
|
71
|
-
it('should
|
|
72
|
-
|
|
73
|
-
.get('/health/detailed')
|
|
74
|
-
.expect(200);
|
|
123
|
+
it('should return 503 when database is disconnected', async () => {
|
|
124
|
+
mongoose.connection.readyState = 0;
|
|
75
125
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
expect(response.body.checks.database).toHaveProperty('type', 'mongodb');
|
|
79
|
-
});
|
|
126
|
+
const req = mockRequest('/health/detailed', { 'x-api-key': 'test-api-key' });
|
|
127
|
+
const res = mockResponse();
|
|
80
128
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
const originalPromiseAll = Promise.all;
|
|
130
|
+
Promise.all = jest.fn().mockResolvedValue([
|
|
131
|
+
{ name: 'github', status: 'healthy', reachable: true, statusCode: 200, responseTime: 100 },
|
|
132
|
+
{ name: 'npm', status: 'healthy', reachable: true, statusCode: 200, responseTime: 150 }
|
|
133
|
+
]);
|
|
85
134
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
135
|
+
const routeHandler = router.stack.find(layer =>
|
|
136
|
+
layer.route && layer.route.path === '/health/detailed'
|
|
137
|
+
).route.stack[0].handle;
|
|
138
|
+
|
|
139
|
+
await routeHandler(req, res);
|
|
140
|
+
|
|
141
|
+
Promise.all = originalPromiseAll;
|
|
142
|
+
|
|
143
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
144
|
+
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
|
145
|
+
status: 'unhealthy'
|
|
146
|
+
}));
|
|
89
147
|
});
|
|
90
148
|
});
|
|
91
149
|
|
|
92
150
|
describe('GET /health/live', () => {
|
|
93
|
-
it('should return
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
151
|
+
it('should return alive status', async () => {
|
|
152
|
+
const req = mockRequest('/health/live', { 'x-api-key': 'test-api-key' });
|
|
153
|
+
const res = mockResponse();
|
|
154
|
+
|
|
155
|
+
const routeHandler = router.stack.find(layer =>
|
|
156
|
+
layer.route && layer.route.path === '/health/live'
|
|
157
|
+
).route.stack[0].handle;
|
|
97
158
|
|
|
98
|
-
|
|
99
|
-
|
|
159
|
+
routeHandler(req, res);
|
|
160
|
+
|
|
161
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
162
|
+
expect(res.json).toHaveBeenCalledWith({
|
|
163
|
+
status: 'alive',
|
|
164
|
+
timestamp: expect.any(String)
|
|
165
|
+
});
|
|
100
166
|
});
|
|
101
167
|
});
|
|
102
168
|
|
|
103
169
|
describe('GET /health/ready', () => {
|
|
104
|
-
it('should return
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
170
|
+
it('should return ready when all checks pass', async () => {
|
|
171
|
+
const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
|
|
172
|
+
const res = mockResponse();
|
|
173
|
+
|
|
174
|
+
const routeHandler = router.stack.find(layer =>
|
|
175
|
+
layer.route && layer.route.path === '/health/ready'
|
|
176
|
+
).route.stack[0].handle;
|
|
177
|
+
|
|
178
|
+
await routeHandler(req, res);
|
|
179
|
+
|
|
180
|
+
expect(res.status).toHaveBeenCalledWith(200);
|
|
181
|
+
expect(res.json).toHaveBeenCalledWith({
|
|
182
|
+
ready: true,
|
|
183
|
+
timestamp: expect.any(String),
|
|
184
|
+
checks: {
|
|
185
|
+
database: true,
|
|
186
|
+
modules: true
|
|
187
|
+
}
|
|
188
|
+
});
|
|
113
189
|
});
|
|
114
190
|
|
|
115
191
|
it('should return 503 when database is not connected', async () => {
|
|
116
|
-
// Mock disconnected database
|
|
117
192
|
mongoose.connection.readyState = 0;
|
|
118
193
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
.expect(503);
|
|
194
|
+
const req = mockRequest('/health/ready', { 'x-api-key': 'test-api-key' });
|
|
195
|
+
const res = mockResponse();
|
|
122
196
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
197
|
+
const routeHandler = router.stack.find(layer =>
|
|
198
|
+
layer.route && layer.route.path === '/health/ready'
|
|
199
|
+
).route.stack[0].handle;
|
|
200
|
+
|
|
201
|
+
await routeHandler(req, res);
|
|
128
202
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// for easier unit testing
|
|
203
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
204
|
+
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
|
|
205
|
+
ready: false
|
|
206
|
+
}));
|
|
207
|
+
});
|
|
135
208
|
});
|
|
136
209
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.404.e9d4980.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@hapi/boom": "^10.0.1",
|
|
7
7
|
"aws-sdk": "^2.1200.0",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"uuid": "^9.0.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
26
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
27
|
-
"@friggframework/test": "2.0.0--canary.
|
|
25
|
+
"@friggframework/eslint-config": "2.0.0--canary.404.e9d4980.0",
|
|
26
|
+
"@friggframework/prettier-config": "2.0.0--canary.404.e9d4980.0",
|
|
27
|
+
"@friggframework/test": "2.0.0--canary.404.e9d4980.0",
|
|
28
28
|
"@types/lodash": "4.17.15",
|
|
29
29
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
30
30
|
"chai": "^4.3.6",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
},
|
|
54
54
|
"homepage": "https://github.com/friggframework/frigg#readme",
|
|
55
55
|
"description": "",
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "e9d4980828d7deda79e33dfbb2fed93cb6fef84d"
|
|
57
57
|
}
|