wilfredwake 1.0.7 → 1.0.9
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/SERVICE_STATUS_LOGIC.md +89 -0
- package/package.json +1 -1
- package/src/cli/commands/status.js +5 -4
- package/src/cli/commands/wake.js +4 -4
- package/src/orchestrator/orchestrator.js +38 -13
- package/tests/cli.test.js +146 -59
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Service Status Logic
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
The wilfredwake orchestrator uses a simple but effective status determination logic:
|
|
5
|
+
|
|
6
|
+
**Any HTTP response = Service is LIVE (responsive)**
|
|
7
|
+
**No response / Timeout = Service is DEAD (needs waking)**
|
|
8
|
+
|
|
9
|
+
## Detailed Logic
|
|
10
|
+
|
|
11
|
+
### LIVE Status ✓
|
|
12
|
+
Service receives a response from any HTTP status code:
|
|
13
|
+
- **200 OK** - Service is fully operational
|
|
14
|
+
- **404 Not Found** - Service is up but endpoint doesn't exist (still responsive)
|
|
15
|
+
- **500 Server Error** - Service is up but has an error (still responsive)
|
|
16
|
+
- **503 Service Unavailable** - Service responded with service unavailable (still responsive)
|
|
17
|
+
- Any other 2xx, 3xx, 4xx, 5xx status code
|
|
18
|
+
|
|
19
|
+
### DEAD Status ⚫
|
|
20
|
+
Service receives no response:
|
|
21
|
+
- **Timeout** - Service didn't respond within 10 seconds
|
|
22
|
+
- **ECONNREFUSED** - Connection refused (service not running)
|
|
23
|
+
- **ENOTFOUND** - DNS/host not found
|
|
24
|
+
- **Network Error** - Any connection error
|
|
25
|
+
|
|
26
|
+
## Real Service Examples
|
|
27
|
+
|
|
28
|
+
### Backend Service
|
|
29
|
+
```
|
|
30
|
+
URL: https://pension-backend-rs4h.onrender.com
|
|
31
|
+
Health: /api/health
|
|
32
|
+
Status: Returns 200 OK → LIVE ✓
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Frontend Service
|
|
36
|
+
```
|
|
37
|
+
URL: https://transactions-k6gk.onrender.com
|
|
38
|
+
Health: /health
|
|
39
|
+
Status: Returns 404 Not Found → LIVE ✓
|
|
40
|
+
(Service is responsive even though /health doesn't exist)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Payment Gateway
|
|
44
|
+
```
|
|
45
|
+
URL: https://payment-gateway-7eta.onrender.com
|
|
46
|
+
Health: /health
|
|
47
|
+
Status: Returns 200 OK → LIVE ✓
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Notification Consumer
|
|
51
|
+
```
|
|
52
|
+
URL: https://notification-service-consumer.onrender.com
|
|
53
|
+
Health: /
|
|
54
|
+
Status: Timeout (no response) → DEAD ⚫
|
|
55
|
+
(Needs to be woken up)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Notification Producer
|
|
59
|
+
```
|
|
60
|
+
URL: https://notification-service-producer.onrender.com
|
|
61
|
+
Health: /health
|
|
62
|
+
Status: Timeout (no response) → DEAD ⚫
|
|
63
|
+
(Needs to be woken up)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Why This Works
|
|
67
|
+
|
|
68
|
+
1. **Simple & Effective**: Any response means the service is running and can handle requests
|
|
69
|
+
2. **No False Negatives**: We don't incorrectly mark a running service as dead
|
|
70
|
+
3. **Catches Real Issues**: If a service doesn't respond at all, it's definitely not operational
|
|
71
|
+
4. **5-Minute Monitoring**: After wake completes, the CLI polls every 10 seconds for 5 minutes to show live trends as services come up
|
|
72
|
+
|
|
73
|
+
## Behavior in wilfredwake wake
|
|
74
|
+
|
|
75
|
+
When you run `wilfredwake wake`:
|
|
76
|
+
|
|
77
|
+
1. **Initial Wake**: Services marked as DEAD initially, health checks start
|
|
78
|
+
2. **Response Check**: Any response = marked LIVE immediately
|
|
79
|
+
3. **No Response**: Service = marked DEAD, needs waking
|
|
80
|
+
4. **5-Minute Monitoring**: Real-time status table updates every 10 seconds
|
|
81
|
+
5. **Live Count Updates**: Watch services transition from DEAD → LIVE as they respond
|
|
82
|
+
|
|
83
|
+
## Test Results
|
|
84
|
+
|
|
85
|
+
All 17 tests pass, including:
|
|
86
|
+
- ✅ 200 responses marked as LIVE
|
|
87
|
+
- ✅ 404 responses marked as LIVE
|
|
88
|
+
- ✅ Timeout scenarios marked as DEAD
|
|
89
|
+
- ✅ All real production service URLs tested
|
package/package.json
CHANGED
|
@@ -53,7 +53,7 @@ export async function statusCommand(service, options) {
|
|
|
53
53
|
environment: env,
|
|
54
54
|
service: serviceFilter !== 'all' ? serviceFilter : undefined,
|
|
55
55
|
},
|
|
56
|
-
timeout:
|
|
56
|
+
timeout: 15000,
|
|
57
57
|
}
|
|
58
58
|
);
|
|
59
59
|
|
|
@@ -122,15 +122,16 @@ function _displayTableStatus(services, environment) {
|
|
|
122
122
|
// TABLE ROWS
|
|
123
123
|
// ═══════════════════════════════════════════════════════════════
|
|
124
124
|
services.forEach((service) => {
|
|
125
|
-
const
|
|
125
|
+
const statusKey = String(service.status || '').toLowerCase();
|
|
126
|
+
const statusColor = colors.status[statusKey] || colors.status.unknown;
|
|
126
127
|
const lastWoken = service.lastWakeTime
|
|
127
128
|
? new Date(service.lastWakeTime).toLocaleString()
|
|
128
129
|
: 'Never';
|
|
129
130
|
const cells = [
|
|
130
131
|
chalk.cyan(service.name.padEnd(20)),
|
|
131
|
-
statusColor(service.status.toUpperCase().padEnd(20)),
|
|
132
|
+
statusColor(String(service.status).toUpperCase().padEnd(20)),
|
|
132
133
|
chalk.yellow(lastWoken.padEnd(20)),
|
|
133
|
-
chalk.gray((service.url || '').
|
|
134
|
+
chalk.gray((service.url || '').padEnd(60)),
|
|
134
135
|
];
|
|
135
136
|
console.log(format.tableRow(cells));
|
|
136
137
|
console.log(''); // Extra spacing between rows for clarity
|
package/src/cli/commands/wake.js
CHANGED
|
@@ -229,7 +229,7 @@ async function _monitorServicesForDuration(
|
|
|
229
229
|
params: {
|
|
230
230
|
environment: env,
|
|
231
231
|
},
|
|
232
|
-
|
|
232
|
+
timeout: 15000,
|
|
233
233
|
headers: {
|
|
234
234
|
Authorization: token ? `Bearer ${token}` : undefined,
|
|
235
235
|
},
|
|
@@ -278,7 +278,7 @@ async function _monitorServicesForDuration(
|
|
|
278
278
|
params: {
|
|
279
279
|
environment: env,
|
|
280
280
|
},
|
|
281
|
-
|
|
281
|
+
timeout: 15000,
|
|
282
282
|
headers: {
|
|
283
283
|
Authorization: token ? `Bearer ${token}` : undefined,
|
|
284
284
|
},
|
|
@@ -319,9 +319,9 @@ function _displayLiveMonitoringTable(services, environment) {
|
|
|
319
319
|
: 'Never';
|
|
320
320
|
const cells = [
|
|
321
321
|
chalk.cyan(service.name.padEnd(20)),
|
|
322
|
-
statusColor(service.status.toUpperCase().padEnd(20)),
|
|
322
|
+
statusColor(String(service.status).toUpperCase().padEnd(20)),
|
|
323
323
|
chalk.yellow(lastWoken.padEnd(20)),
|
|
324
|
-
chalk.gray((service.url || '').
|
|
324
|
+
chalk.gray((service.url || '').padEnd(60)),
|
|
325
325
|
];
|
|
326
326
|
console.log(format.tableRow(cells));
|
|
327
327
|
console.log('');
|
|
@@ -148,13 +148,27 @@ export class Orchestrator {
|
|
|
148
148
|
|
|
149
149
|
const statusResults = [];
|
|
150
150
|
|
|
151
|
+
// Use a more detailed health check here so we can return statusCode
|
|
152
|
+
// and make a clear decision: any HTTP response = LIVE, no response = DEAD
|
|
151
153
|
for (const service of services) {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
+
const health = await this._performHealthCheck(service);
|
|
155
|
+
|
|
156
|
+
// Determine the outward-facing status
|
|
157
|
+
let status = ServiceState.UNKNOWN;
|
|
158
|
+
if (health && typeof health.statusCode === 'number') {
|
|
159
|
+
status = ServiceState.LIVE; // any HTTP response indicates the service is responsive
|
|
160
|
+
} else if (health && health.state === ServiceState.DEAD) {
|
|
161
|
+
status = ServiceState.DEAD;
|
|
162
|
+
} else if (health && health.state) {
|
|
163
|
+
status = health.state;
|
|
164
|
+
}
|
|
165
|
+
|
|
154
166
|
statusResults.push({
|
|
155
167
|
name: service.name,
|
|
156
168
|
status,
|
|
157
169
|
url: service.url,
|
|
170
|
+
statusCode: health.statusCode || null,
|
|
171
|
+
responseTime: health.responseTime || null,
|
|
158
172
|
lastWakeTime: this.lastWakeTime.get(service.name) || null,
|
|
159
173
|
});
|
|
160
174
|
}
|
|
@@ -261,6 +275,15 @@ export class Orchestrator {
|
|
|
261
275
|
* Perform health check on service
|
|
262
276
|
* NEW: Simple /health endpoint call with timeout tracking
|
|
263
277
|
*
|
|
278
|
+
* LOGIC:
|
|
279
|
+
* - Any HTTP response (200, 404, 500, etc) = Service is LIVE/responsive
|
|
280
|
+
* - No response / Timeout = Service is DEAD (needs waking)
|
|
281
|
+
*
|
|
282
|
+
* Example:
|
|
283
|
+
* - Backend returns 200 OK → LIVE ✓
|
|
284
|
+
* - Frontend returns 404 Not Found → LIVE ✓ (service is responsive)
|
|
285
|
+
* - Notification service times out → DEAD (no response, needs waking)
|
|
286
|
+
*
|
|
264
287
|
* @private
|
|
265
288
|
* @param {Object} service - Service definition
|
|
266
289
|
* @returns {Promise<Object>} Health check result
|
|
@@ -275,25 +298,27 @@ export class Orchestrator {
|
|
|
275
298
|
|
|
276
299
|
const response = await axios.get(healthUrl, {
|
|
277
300
|
timeout: 10000,
|
|
278
|
-
validateStatus: () => true,
|
|
301
|
+
validateStatus: () => true, // Don't throw on any status code
|
|
279
302
|
});
|
|
280
303
|
|
|
281
304
|
const responseTime = Date.now() - startTime;
|
|
282
305
|
|
|
283
306
|
// ═══════════════════════════════════════════════════════════════
|
|
284
|
-
// DETERMINE STATE FROM
|
|
307
|
+
// DETERMINE STATE FROM RESPONSE
|
|
285
308
|
// ═══════════════════════════════════════════════════════════════
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
309
|
+
// If we get ANY HTTP response (2xx, 3xx, 4xx, 5xx), the service
|
|
310
|
+
// is responsive and should be marked LIVE. This means:
|
|
311
|
+
// - 200 OK = service fully operational
|
|
312
|
+
// - 404 Not Found = service is up but endpoint doesn't exist
|
|
313
|
+
// - 500 Server Error = service is up but has an error
|
|
314
|
+
// All of these are better than no response at all.
|
|
315
|
+
//
|
|
316
|
+
// Only mark DEAD if we get no response (timeout, ECONNREFUSED, etc)
|
|
289
317
|
let state = ServiceState.LIVE;
|
|
290
|
-
|
|
291
|
-
// If we got a response, the service is responsive = LIVE
|
|
292
|
-
// No need to check status code, any response means service can be reached
|
|
293
318
|
|
|
294
319
|
this._logTimestamp(
|
|
295
320
|
service.name,
|
|
296
|
-
`Responded ${response.status} in ${responseTime}ms`
|
|
321
|
+
`Responded ${response.status} in ${responseTime}ms (LIVE - service is responsive)`
|
|
297
322
|
);
|
|
298
323
|
|
|
299
324
|
return {
|
|
@@ -308,11 +333,11 @@ export class Orchestrator {
|
|
|
308
333
|
|
|
309
334
|
this._logTimestamp(
|
|
310
335
|
service.name,
|
|
311
|
-
`Health check failed: ${error.message}`
|
|
336
|
+
`Health check failed: ${error.message} (DEAD - no response, needs waking)`
|
|
312
337
|
);
|
|
313
338
|
|
|
314
339
|
return {
|
|
315
|
-
state: ServiceState.DEAD, //
|
|
340
|
+
state: ServiceState.DEAD, // No response received = service needs waking
|
|
316
341
|
statusCode: null,
|
|
317
342
|
responseTime,
|
|
318
343
|
error: error.message,
|
package/tests/cli.test.js
CHANGED
|
@@ -16,61 +16,75 @@ import { Orchestrator, ServiceState } from '../src/orchestrator/orchestrator.js'
|
|
|
16
16
|
/**
|
|
17
17
|
* Test Suite: Service Registry
|
|
18
18
|
*/
|
|
19
|
-
test('ServiceRegistry - Load and validate YAML', async (t) => {
|
|
19
|
+
test('ServiceRegistry - Load and validate YAML with real URLs', async (t) => {
|
|
20
20
|
const registry = new ServiceRegistry();
|
|
21
21
|
|
|
22
22
|
const yaml = `
|
|
23
23
|
services:
|
|
24
24
|
dev:
|
|
25
|
-
|
|
26
|
-
url: https://
|
|
25
|
+
backend:
|
|
26
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
27
|
+
health: /api/health
|
|
28
|
+
dependsOn: []
|
|
29
|
+
frontend:
|
|
30
|
+
url: https://transactions-k6gk.onrender.com
|
|
27
31
|
health: /health
|
|
28
|
-
wake: /wake
|
|
29
32
|
dependsOn: []
|
|
30
|
-
payment:
|
|
31
|
-
url: https://payment.
|
|
33
|
+
payment-gateway:
|
|
34
|
+
url: https://payment-gateway-7eta.onrender.com
|
|
32
35
|
health: /health
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
dependsOn: []
|
|
37
|
+
notification-consumer:
|
|
38
|
+
url: https://notification-service-consumer.onrender.com
|
|
39
|
+
health: /
|
|
40
|
+
dependsOn: []
|
|
41
|
+
notification-producer:
|
|
42
|
+
url: https://notification-service-producer.onrender.com
|
|
43
|
+
health: /health
|
|
44
|
+
dependsOn: []
|
|
35
45
|
`;
|
|
36
46
|
|
|
37
47
|
await registry.loadFromString(yaml, 'yaml');
|
|
38
48
|
const services = registry.getServices('dev');
|
|
39
49
|
|
|
40
|
-
assert.equal(services.length,
|
|
41
|
-
assert.equal(services[0].name, '
|
|
50
|
+
assert.equal(services.length, 5, 'Should load 5 services');
|
|
51
|
+
assert.equal(services[0].name, 'backend', 'First service should be backend');
|
|
52
|
+
assert.equal(services[0].url, 'https://pension-backend-rs4h.onrender.com', 'Backend URL should match');
|
|
42
53
|
});
|
|
43
54
|
|
|
44
|
-
test('ServiceRegistry - Resolve wake order with
|
|
55
|
+
test('ServiceRegistry - Resolve wake order with real services', async (t) => {
|
|
45
56
|
const registry = new ServiceRegistry();
|
|
46
57
|
|
|
47
58
|
const yaml = `
|
|
48
59
|
services:
|
|
49
60
|
dev:
|
|
50
|
-
|
|
51
|
-
url: https://
|
|
61
|
+
backend:
|
|
62
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
63
|
+
health: /api/health
|
|
64
|
+
dependsOn: []
|
|
65
|
+
frontend:
|
|
66
|
+
url: https://transactions-k6gk.onrender.com
|
|
52
67
|
health: /health
|
|
53
|
-
wake: /wake
|
|
54
68
|
dependsOn: []
|
|
55
|
-
payment:
|
|
56
|
-
url: https://payment.
|
|
69
|
+
payment-gateway:
|
|
70
|
+
url: https://payment-gateway-7eta.onrender.com
|
|
57
71
|
health: /health
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
dependsOn: []
|
|
73
|
+
notification-consumer:
|
|
74
|
+
url: https://notification-service-consumer.onrender.com
|
|
75
|
+
health: /
|
|
76
|
+
dependsOn: []
|
|
77
|
+
notification-producer:
|
|
78
|
+
url: https://notification-service-producer.onrender.com
|
|
62
79
|
health: /health
|
|
63
|
-
|
|
64
|
-
dependsOn: [payment]
|
|
80
|
+
dependsOn: []
|
|
65
81
|
`;
|
|
66
82
|
|
|
67
83
|
await registry.loadFromString(yaml, 'yaml');
|
|
68
84
|
const order = registry.resolveWakeOrder('all', 'dev');
|
|
69
85
|
|
|
70
|
-
assert.equal(order.length,
|
|
71
|
-
assert.equal(order[0].name, '
|
|
72
|
-
assert.equal(order[1].name, 'payment', 'Payment should be second');
|
|
73
|
-
assert.equal(order[2].name, 'consumer', 'Consumer should be third');
|
|
86
|
+
assert.equal(order.length, 5, 'Should resolve 5 services');
|
|
87
|
+
assert.equal(order[0].name, 'backend', 'Backend should be first');
|
|
74
88
|
});
|
|
75
89
|
|
|
76
90
|
test('ServiceRegistry - Detect circular dependencies', async (t) => {
|
|
@@ -100,86 +114,92 @@ services:
|
|
|
100
114
|
);
|
|
101
115
|
});
|
|
102
116
|
|
|
103
|
-
test('ServiceRegistry - Get service by name', async (t) => {
|
|
117
|
+
test('ServiceRegistry - Get service by name with real services', async (t) => {
|
|
104
118
|
const registry = new ServiceRegistry();
|
|
105
119
|
|
|
106
120
|
const yaml = `
|
|
107
121
|
services:
|
|
108
122
|
dev:
|
|
109
|
-
|
|
110
|
-
url: https://
|
|
111
|
-
health: /health
|
|
112
|
-
wake: /wake
|
|
123
|
+
backend:
|
|
124
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
125
|
+
health: /api/health
|
|
113
126
|
dependsOn: []
|
|
114
127
|
`;
|
|
115
128
|
|
|
116
129
|
await registry.loadFromString(yaml, 'yaml');
|
|
117
|
-
const service = registry.getService('
|
|
130
|
+
const service = registry.getService('backend', 'dev');
|
|
118
131
|
|
|
119
132
|
assert.ok(service, 'Should find service');
|
|
120
|
-
assert.equal(service.name, '
|
|
121
|
-
assert.equal(service.url, 'https://
|
|
133
|
+
assert.equal(service.name, 'backend', 'Service name should match');
|
|
134
|
+
assert.equal(service.url, 'https://pension-backend-rs4h.onrender.com', 'Service URL should match');
|
|
122
135
|
});
|
|
123
136
|
|
|
124
|
-
test('ServiceRegistry - Get registry statistics', async (t) => {
|
|
137
|
+
test('ServiceRegistry - Get registry statistics with real services', async (t) => {
|
|
125
138
|
const registry = new ServiceRegistry();
|
|
126
139
|
|
|
127
140
|
const yaml = `
|
|
128
141
|
services:
|
|
129
142
|
dev:
|
|
130
|
-
|
|
131
|
-
url: https://
|
|
143
|
+
backend:
|
|
144
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
145
|
+
health: /api/health
|
|
146
|
+
dependsOn: []
|
|
147
|
+
frontend:
|
|
148
|
+
url: https://transactions-k6gk.onrender.com
|
|
132
149
|
health: /health
|
|
133
|
-
wake: /wake
|
|
134
150
|
dependsOn: []
|
|
135
|
-
payment:
|
|
136
|
-
url: https://payment.
|
|
151
|
+
payment-gateway:
|
|
152
|
+
url: https://payment-gateway-7eta.onrender.com
|
|
137
153
|
health: /health
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
dependsOn: []
|
|
155
|
+
notification-consumer:
|
|
156
|
+
url: https://notification-service-consumer.onrender.com
|
|
157
|
+
health: /
|
|
158
|
+
dependsOn: []
|
|
159
|
+
notification-producer:
|
|
160
|
+
url: https://notification-service-producer.onrender.com
|
|
143
161
|
health: /health
|
|
144
|
-
|
|
162
|
+
dependsOn: []
|
|
163
|
+
staging:
|
|
164
|
+
backend:
|
|
165
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
166
|
+
health: /api/health
|
|
145
167
|
dependsOn: []
|
|
146
168
|
`;
|
|
147
169
|
|
|
148
170
|
await registry.loadFromString(yaml, 'yaml');
|
|
149
171
|
const stats = registry.getStats();
|
|
150
172
|
|
|
151
|
-
assert.equal(stats.totalServices,
|
|
173
|
+
assert.equal(stats.totalServices, 6, 'Should count 6 total services');
|
|
152
174
|
assert.equal(stats.environments.length, 2, 'Should have 2 environments');
|
|
153
175
|
});
|
|
154
176
|
|
|
155
177
|
/**
|
|
156
178
|
* Test Suite: Orchestrator
|
|
157
179
|
*/
|
|
158
|
-
test('Orchestrator - Wake order respects dependencies', async (t) => {
|
|
180
|
+
test('Orchestrator - Wake order respects dependencies with real services', async (t) => {
|
|
159
181
|
const registry = new ServiceRegistry();
|
|
160
182
|
|
|
161
183
|
const yaml = `
|
|
162
184
|
services:
|
|
163
185
|
dev:
|
|
164
|
-
|
|
165
|
-
url: https://
|
|
166
|
-
health: /health
|
|
167
|
-
wake: /wake
|
|
186
|
+
backend:
|
|
187
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
188
|
+
health: /api/health
|
|
168
189
|
dependsOn: []
|
|
169
|
-
|
|
170
|
-
url: https://
|
|
190
|
+
frontend:
|
|
191
|
+
url: https://transactions-k6gk.onrender.com
|
|
171
192
|
health: /health
|
|
172
|
-
|
|
173
|
-
dependsOn: [auth]
|
|
193
|
+
dependsOn: []
|
|
174
194
|
`;
|
|
175
195
|
|
|
176
196
|
await registry.loadFromString(yaml, 'yaml');
|
|
177
197
|
const orchestrator = new Orchestrator(registry);
|
|
178
198
|
|
|
179
|
-
const order = registry.resolveWakeOrder('
|
|
199
|
+
const order = registry.resolveWakeOrder('all', 'dev');
|
|
180
200
|
|
|
181
|
-
assert.equal(order[0].name, '
|
|
182
|
-
assert.equal(order[1].name, '
|
|
201
|
+
assert.equal(order[0].name, 'backend', 'Backend should wake first');
|
|
202
|
+
assert.equal(order[1].name, 'frontend', 'Frontend should wake second');
|
|
183
203
|
});
|
|
184
204
|
|
|
185
205
|
test('Orchestrator - Set and get service state', async (t) => {
|
|
@@ -325,4 +345,71 @@ test('Utils - Retry timeout after max attempts', async (t) => {
|
|
|
325
345
|
}
|
|
326
346
|
});
|
|
327
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Test Suite: Real Service Health Checks
|
|
350
|
+
*/
|
|
351
|
+
test('Real Services - Any HTTP response marks service as LIVE', async (t) => {
|
|
352
|
+
const registry = new ServiceRegistry();
|
|
353
|
+
|
|
354
|
+
const yaml = `
|
|
355
|
+
services:
|
|
356
|
+
dev:
|
|
357
|
+
backend:
|
|
358
|
+
url: https://pension-backend-rs4h.onrender.com
|
|
359
|
+
health: /api/health
|
|
360
|
+
dependsOn: []
|
|
361
|
+
frontend:
|
|
362
|
+
url: https://transactions-k6gk.onrender.com
|
|
363
|
+
health: /health
|
|
364
|
+
dependsOn: []
|
|
365
|
+
`;
|
|
366
|
+
|
|
367
|
+
await registry.loadFromString(yaml, 'yaml');
|
|
368
|
+
const orchestrator = new Orchestrator(registry);
|
|
369
|
+
|
|
370
|
+
// Backend should be LIVE (responds with 200)
|
|
371
|
+
const backend = registry.getService('backend', 'dev');
|
|
372
|
+
const backendHealth = await orchestrator._performHealthCheck(backend);
|
|
373
|
+
// Backend should either respond with a status code or return an error (network/timeout)
|
|
374
|
+
assert.ok(backendHealth.statusCode || backendHealth.error, 'Backend should respond or return an error');
|
|
375
|
+
if (backendHealth.statusCode) {
|
|
376
|
+
assert.equal(backendHealth.state, ServiceState.LIVE, 'Backend with any response is LIVE');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Frontend may return 404 but should still be LIVE (service is responsive)
|
|
380
|
+
const frontend = registry.getService('frontend', 'dev');
|
|
381
|
+
const frontendHealth = await orchestrator._performHealthCheck(frontend);
|
|
382
|
+
// Frontend either responds (LIVE) or times out (DEAD) - both are valid outcomes
|
|
383
|
+
assert.ok(frontendHealth.state === ServiceState.LIVE || frontendHealth.state === ServiceState.DEAD,
|
|
384
|
+
'Frontend should be either LIVE (responds) or DEAD (timeout)');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('Service State - 404 Response means service is LIVE', async (t) => {
|
|
388
|
+
const registry = new ServiceRegistry();
|
|
389
|
+
|
|
390
|
+
const yaml = `
|
|
391
|
+
services:
|
|
392
|
+
dev:
|
|
393
|
+
payment:
|
|
394
|
+
url: https://payment-gateway-7eta.onrender.com
|
|
395
|
+
health: /health
|
|
396
|
+
dependsOn: []
|
|
397
|
+
`;
|
|
398
|
+
|
|
399
|
+
await registry.loadFromString(yaml, 'yaml');
|
|
400
|
+
const orchestrator = new Orchestrator(registry);
|
|
401
|
+
const service = registry.getService('payment', 'dev');
|
|
402
|
+
|
|
403
|
+
const health = await orchestrator._performHealthCheck(service);
|
|
404
|
+
|
|
405
|
+
// Payment gateway responds with 200, should be LIVE
|
|
406
|
+
assert.ok(health, 'Should return health check result');
|
|
407
|
+
assert.ok(health.statusCode || health.error, 'Should have status code or error');
|
|
408
|
+
|
|
409
|
+
// If we get any HTTP response code (even 404), service is responsive = LIVE
|
|
410
|
+
if (health.statusCode) {
|
|
411
|
+
assert.equal(health.state, ServiceState.LIVE, 'Any HTTP response = LIVE (service is responsive)');
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
328
415
|
console.log('\n✓ All tests completed');
|