@onlineapps/conn-orch-validator 2.0.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.
Files changed (37) hide show
  1. package/README.md +78 -0
  2. package/TESTING_STRATEGY.md +92 -0
  3. package/docs/DESIGN.md +134 -0
  4. package/examples/service-wrapper-usage.js +250 -0
  5. package/examples/three-tier-testing.js +144 -0
  6. package/jest.config.js +23 -0
  7. package/onlineapps-conn-e2e-testing-1.0.0.tgz +0 -0
  8. package/package.json +43 -0
  9. package/src/CookbookTestRunner.js +434 -0
  10. package/src/CookbookTestUtils.js +237 -0
  11. package/src/ServiceReadinessValidator.js +430 -0
  12. package/src/ServiceTestHarness.js +256 -0
  13. package/src/ServiceValidator.js +387 -0
  14. package/src/TestOrchestrator.js +727 -0
  15. package/src/ValidationOrchestrator.js +506 -0
  16. package/src/WorkflowTestRunner.js +396 -0
  17. package/src/helpers/README.md +235 -0
  18. package/src/helpers/createPreValidationTests.js +321 -0
  19. package/src/helpers/createServiceReadinessTests.js +245 -0
  20. package/src/index.js +62 -0
  21. package/src/mocks/MockMQClient.js +176 -0
  22. package/src/mocks/MockRegistry.js +164 -0
  23. package/src/mocks/MockStorage.js +186 -0
  24. package/src/validators/ServiceStructureValidator.js +487 -0
  25. package/src/validators/ValidationProofGenerator.js +79 -0
  26. package/test-mq-flow.js +72 -0
  27. package/test-orchestrator.js +95 -0
  28. package/tests/component/testing-framework-integration.test.js +313 -0
  29. package/tests/integration/ServiceReadiness.test.js +265 -0
  30. package/tests/monitoring-e2e.test.js +315 -0
  31. package/tests/run-example.js +257 -0
  32. package/tests/unit/CookbookTestRunner.test.js +353 -0
  33. package/tests/unit/MockMQClient.test.js +190 -0
  34. package/tests/unit/MockRegistry.test.js +233 -0
  35. package/tests/unit/MockStorage.test.js +257 -0
  36. package/tests/unit/ServiceValidator.test.js +429 -0
  37. package/tests/unit/WorkflowTestRunner.test.js +546 -0
@@ -0,0 +1,315 @@
1
+ /**
2
+ * End-to-End tests for monitoring infrastructure
3
+ * Tests the complete flow from service through monitoring to telemetry
4
+ */
5
+
6
+ const amqp = require('amqplib');
7
+ const axios = require('axios');
8
+
9
+ describe('Monitoring E2E Tests @e2e', () => {
10
+ let connection;
11
+ let channel;
12
+ const RABBITMQ_URL = process.env.AMQP_URL || 'amqp://localhost:5672';
13
+ const HELLO_SERVICE_URL = 'http://localhost:33199';
14
+
15
+ beforeAll(async () => {
16
+ try {
17
+ connection = await amqp.connect(RABBITMQ_URL);
18
+ channel = await connection.createChannel();
19
+
20
+ // Setup test exchange and queue
21
+ await channel.assertExchange('test.exchange', 'topic', { durable: false });
22
+ await channel.assertQueue('test.monitoring', { durable: false });
23
+ await channel.bindQueue('test.monitoring', 'test.exchange', 'monitoring.#');
24
+ } catch (error) {
25
+ console.error('Failed to connect to RabbitMQ:', error);
26
+ }
27
+ });
28
+
29
+ afterAll(async () => {
30
+ if (channel) await channel.close();
31
+ if (connection) await connection.close();
32
+ });
33
+
34
+ describe('Service to Monitoring Flow', () => {
35
+ test('should generate monitoring data from service call', async () => {
36
+ // Skip if services not available
37
+ if (!connection) {
38
+ console.log('Skipping - RabbitMQ not available');
39
+ return;
40
+ }
41
+
42
+ // Send cookbook message to service
43
+ const cookbook = {
44
+ id: 'e2e-test-1',
45
+ type: 'hello',
46
+ name: 'good-day',
47
+ version: '1.0.0',
48
+ metadata: {
49
+ workflowId: 'e2e-wf-1',
50
+ timestamp: new Date().toISOString()
51
+ }
52
+ };
53
+
54
+ // Send to service via MQ
55
+ const replyQueue = await channel.assertQueue('', { exclusive: true });
56
+ const correlationId = 'e2e-corr-1';
57
+
58
+ await channel.sendToQueue(
59
+ 'hello-service',
60
+ Buffer.from(JSON.stringify(cookbook)),
61
+ {
62
+ correlationId,
63
+ replyTo: replyQueue.queue
64
+ }
65
+ );
66
+
67
+ // Wait for response
68
+ const response = await new Promise((resolve, reject) => {
69
+ const timeout = setTimeout(() => {
70
+ reject(new Error('Timeout waiting for response'));
71
+ }, 5000);
72
+
73
+ channel.consume(replyQueue.queue, (msg) => {
74
+ if (msg.properties.correlationId === correlationId) {
75
+ clearTimeout(timeout);
76
+ resolve(JSON.parse(msg.content.toString()));
77
+ }
78
+ }, { noAck: true });
79
+ });
80
+
81
+ expect(response).toBeDefined();
82
+ expect(response.status).toBeDefined();
83
+ });
84
+
85
+ test('should track workflow through monitoring', async () => {
86
+ // Direct HTTP call to service
87
+ try {
88
+ const response = await axios.post(`${HELLO_SERVICE_URL}/api/workflow`, {
89
+ workflowId: 'e2e-wf-2',
90
+ operation: 'good-day',
91
+ data: {
92
+ message: 'E2E test'
93
+ }
94
+ });
95
+
96
+ expect(response.status).toBe(200);
97
+ expect(response.data).toBeDefined();
98
+ } catch (error) {
99
+ // Service might not be running
100
+ console.log('Service not available, skipping HTTP test');
101
+ }
102
+ });
103
+ });
104
+
105
+ describe('Monitoring Data Collection', () => {
106
+ test('should collect logs from monitoring', async () => {
107
+ if (!channel) {
108
+ console.log('Skipping - no channel available');
109
+ return;
110
+ }
111
+
112
+ // Setup consumer for monitoring messages
113
+ const messages = [];
114
+
115
+ await channel.consume('test.monitoring', (msg) => {
116
+ if (msg) {
117
+ messages.push(JSON.parse(msg.content.toString()));
118
+ channel.ack(msg);
119
+ }
120
+ });
121
+
122
+ // Trigger some monitoring events
123
+ const testCookbook = {
124
+ id: 'monitor-test-1',
125
+ type: 'hello',
126
+ name: 'test-monitoring',
127
+ version: '1.0.0'
128
+ };
129
+
130
+ await channel.sendToQueue(
131
+ 'hello-service',
132
+ Buffer.from(JSON.stringify(testCookbook))
133
+ );
134
+
135
+ // Wait for messages
136
+ await new Promise(resolve => setTimeout(resolve, 1000));
137
+
138
+ // Check if we received monitoring data
139
+ if (messages.length > 0) {
140
+ expect(messages[0]).toHaveProperty('timestamp');
141
+ expect(messages[0]).toHaveProperty('level');
142
+ }
143
+ });
144
+
145
+ test('should collect metrics from monitoring', async () => {
146
+ // This would connect to metrics endpoint if available
147
+ // For now, we just verify the structure
148
+
149
+ const metricsData = {
150
+ timestamp: new Date().toISOString(),
151
+ metric: 'requests.total',
152
+ value: 1,
153
+ labels: {
154
+ service: 'hello-service',
155
+ endpoint: '/good-day',
156
+ status: 'success'
157
+ }
158
+ };
159
+
160
+ expect(metricsData).toHaveProperty('timestamp');
161
+ expect(metricsData).toHaveProperty('metric');
162
+ expect(metricsData).toHaveProperty('value');
163
+ expect(metricsData.labels).toHaveProperty('service');
164
+ });
165
+ });
166
+
167
+ describe('Error Tracking E2E', () => {
168
+ test('should track errors through monitoring system', async () => {
169
+ if (!channel) {
170
+ console.log('Skipping - no channel available');
171
+ return;
172
+ }
173
+
174
+ // Send invalid cookbook to trigger error
175
+ const invalidCookbook = {
176
+ // Missing required fields
177
+ type: 'invalid'
178
+ };
179
+
180
+ const errorQueue = await channel.assertQueue('', { exclusive: true });
181
+ const correlationId = 'error-test-1';
182
+
183
+ await channel.sendToQueue(
184
+ 'hello-service',
185
+ Buffer.from(JSON.stringify(invalidCookbook)),
186
+ {
187
+ correlationId,
188
+ replyTo: errorQueue.queue
189
+ }
190
+ );
191
+
192
+ // Wait for error response
193
+ const errorResponse = await new Promise((resolve) => {
194
+ const timeout = setTimeout(() => {
195
+ resolve({ error: 'timeout' });
196
+ }, 2000);
197
+
198
+ channel.consume(errorQueue.queue, (msg) => {
199
+ if (msg && msg.properties.correlationId === correlationId) {
200
+ clearTimeout(timeout);
201
+ resolve(JSON.parse(msg.content.toString()));
202
+ }
203
+ }, { noAck: true });
204
+ });
205
+
206
+ // Should receive error response or timeout
207
+ expect(errorResponse).toBeDefined();
208
+ });
209
+ });
210
+
211
+ describe('Performance Monitoring E2E', () => {
212
+ test('should measure end-to-end latency', async () => {
213
+ if (!channel) {
214
+ console.log('Skipping - no channel available');
215
+ return;
216
+ }
217
+
218
+ const startTime = Date.now();
219
+
220
+ const cookbook = {
221
+ id: 'perf-test-1',
222
+ type: 'hello',
223
+ name: 'good-day',
224
+ version: '1.0.0'
225
+ };
226
+
227
+ const perfQueue = await channel.assertQueue('', { exclusive: true });
228
+ const correlationId = 'perf-corr-1';
229
+
230
+ await channel.sendToQueue(
231
+ 'hello-service',
232
+ Buffer.from(JSON.stringify(cookbook)),
233
+ {
234
+ correlationId,
235
+ replyTo: perfQueue.queue
236
+ }
237
+ );
238
+
239
+ await new Promise((resolve) => {
240
+ const timeout = setTimeout(resolve, 2000);
241
+
242
+ channel.consume(perfQueue.queue, (msg) => {
243
+ if (msg && msg.properties.correlationId === correlationId) {
244
+ clearTimeout(timeout);
245
+ resolve();
246
+ }
247
+ }, { noAck: true });
248
+ });
249
+
250
+ const latency = Date.now() - startTime;
251
+
252
+ // E2E latency should be reasonable
253
+ expect(latency).toBeLessThan(5000);
254
+
255
+ console.log(`E2E latency: ${latency}ms`);
256
+ });
257
+
258
+ test('should handle concurrent requests', async () => {
259
+ if (!channel) {
260
+ console.log('Skipping - no channel available');
261
+ return;
262
+ }
263
+
264
+ const concurrentCount = 10;
265
+ const promises = [];
266
+
267
+ for (let i = 0; i < concurrentCount; i++) {
268
+ const cookbook = {
269
+ id: `concurrent-${i}`,
270
+ type: 'hello',
271
+ name: 'good-day',
272
+ version: '1.0.0'
273
+ };
274
+
275
+ const promise = channel.sendToQueue(
276
+ 'hello-service',
277
+ Buffer.from(JSON.stringify(cookbook))
278
+ );
279
+
280
+ promises.push(promise);
281
+ }
282
+
283
+ await Promise.all(promises);
284
+
285
+ // All messages should be sent successfully
286
+ expect(promises.length).toBe(concurrentCount);
287
+ });
288
+ });
289
+
290
+ describe('Monitoring Health Check', () => {
291
+ test('should verify monitoring infrastructure is healthy', async () => {
292
+ // Check RabbitMQ connection
293
+ expect(connection).toBeDefined();
294
+
295
+ // Check if monitoring exchanges exist
296
+ if (channel) {
297
+ try {
298
+ await channel.checkExchange('telemetry.exchange');
299
+ console.log('Telemetry exchange exists');
300
+ } catch (error) {
301
+ console.log('Telemetry exchange not found (expected in test env)');
302
+ }
303
+ }
304
+
305
+ // Check service health endpoint if available
306
+ try {
307
+ const health = await axios.get(`${HELLO_SERVICE_URL}/health`);
308
+ expect(health.status).toBe(200);
309
+ expect(health.data.status).toBe('ok');
310
+ } catch (error) {
311
+ console.log('Service health check not available');
312
+ }
313
+ });
314
+ });
315
+ });
@@ -0,0 +1,257 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Simple test runner to demonstrate connector-testing functionality
5
+ */
6
+
7
+ const { TestOrchestrator, ServiceTestHarness, ServiceReadinessValidator } = require('../src');
8
+ const express = require('express');
9
+ const path = require('path');
10
+
11
+ // Create a minimal test service
12
+ function createTestService() {
13
+ const app = express();
14
+ app.use(express.json());
15
+
16
+ app.get('/health', (req, res) => {
17
+ res.json({
18
+ status: 'healthy',
19
+ service: 'test-service',
20
+ timestamp: new Date().toISOString()
21
+ });
22
+ });
23
+
24
+ app.get('/good-day', (req, res) => {
25
+ const name = req.query.name || 'World';
26
+ res.json({
27
+ message: `Good day, ${name}!`,
28
+ timestamp: new Date().toISOString()
29
+ });
30
+ });
31
+
32
+ app.post('/process', (req, res) => {
33
+ res.json({
34
+ processed: true,
35
+ input: req.body,
36
+ output: { transformed: true }
37
+ });
38
+ });
39
+
40
+ return app;
41
+ }
42
+
43
+ // Create OpenAPI spec
44
+ function createOpenApiSpec() {
45
+ return {
46
+ openapi: '3.0.0',
47
+ info: {
48
+ title: 'Test Service',
49
+ version: '1.0.0',
50
+ description: 'Service for testing connector-testing'
51
+ },
52
+ paths: {
53
+ '/health': {
54
+ get: {
55
+ operationId: 'healthCheck',
56
+ summary: 'Health check endpoint',
57
+ responses: {
58
+ '200': {
59
+ description: 'Service is healthy',
60
+ content: {
61
+ 'application/json': {
62
+ schema: {
63
+ type: 'object',
64
+ properties: {
65
+ status: { type: 'string' },
66
+ service: { type: 'string' },
67
+ timestamp: { type: 'string' }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ },
76
+ '/good-day': {
77
+ get: {
78
+ operationId: 'goodDay',
79
+ summary: 'Greeting endpoint',
80
+ parameters: [
81
+ {
82
+ name: 'name',
83
+ in: 'query',
84
+ required: false,
85
+ schema: { type: 'string' }
86
+ }
87
+ ],
88
+ responses: {
89
+ '200': {
90
+ description: 'Greeting response',
91
+ content: {
92
+ 'application/json': {
93
+ schema: {
94
+ type: 'object',
95
+ properties: {
96
+ message: { type: 'string' },
97
+ timestamp: { type: 'string' }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ }
105
+ },
106
+ '/process': {
107
+ post: {
108
+ operationId: 'processData',
109
+ summary: 'Process data',
110
+ requestBody: {
111
+ content: {
112
+ 'application/json': {
113
+ schema: {
114
+ type: 'object'
115
+ }
116
+ }
117
+ }
118
+ },
119
+ responses: {
120
+ '200': {
121
+ description: 'Processing result',
122
+ content: {
123
+ 'application/json': {
124
+ schema: {
125
+ type: 'object',
126
+ properties: {
127
+ processed: { type: 'boolean' },
128
+ input: { type: 'object' },
129
+ output: { type: 'object' }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ };
140
+ }
141
+
142
+ async function runTests() {
143
+ console.log('=== Connector-Testing Demo ===\n');
144
+
145
+ const app = createTestService();
146
+ const openApiSpec = createOpenApiSpec();
147
+
148
+ // Test 1: Service Test Harness
149
+ console.log('1. Testing with ServiceTestHarness (with mocks)...\n');
150
+
151
+ const harness = new ServiceTestHarness({
152
+ service: app,
153
+ serviceName: 'test-service',
154
+ openApiSpec,
155
+ mockInfrastructure: true
156
+ });
157
+
158
+ await harness.start();
159
+ console.log(` ✓ Service started at: ${harness.baseUrl}`);
160
+
161
+ // Test API calls
162
+ const healthResponse = await harness.callApi('GET', '/health');
163
+ console.log(` ✓ Health check: ${healthResponse.status}`);
164
+
165
+ const greetingResponse = await harness.callApi('GET', '/good-day', null, {});
166
+ console.log(` ✓ Greeting: ${greetingResponse.message}`);
167
+
168
+ // Test workflow simulation
169
+ const workflow = await harness.simulateWorkflow({
170
+ steps: [
171
+ {
172
+ id: 'step1',
173
+ type: 'task',
174
+ service: 'test-service',
175
+ operation: 'healthCheck'
176
+ },
177
+ {
178
+ id: 'step2',
179
+ type: 'task',
180
+ service: 'test-service',
181
+ operation: 'processData',
182
+ input: { test: 'data' }
183
+ }
184
+ ]
185
+ });
186
+ console.log(` ✓ Workflow completed: ${workflow.completed}`);
187
+
188
+ // Check mock infrastructure
189
+ const messages = harness.getPublishedMessages();
190
+ console.log(` ✓ Messages published: ${messages.length}`);
191
+
192
+ const registration = harness.getServiceRegistration();
193
+ console.log(` ✓ Service registered: ${registration.name}`);
194
+
195
+ await harness.stop();
196
+ console.log(' ✓ Harness stopped\n');
197
+
198
+ // Test 2: Service Readiness Validator
199
+ console.log('2. Testing with ServiceReadinessValidator...\n');
200
+
201
+ // Start service for validation
202
+ const server = app.listen(4444);
203
+
204
+ const validator = new ServiceReadinessValidator();
205
+ const readiness = await validator.validateReadiness({
206
+ name: 'test-service',
207
+ url: 'http://localhost:4444',
208
+ openApiSpec,
209
+ healthEndpoint: '/health'
210
+ });
211
+
212
+ console.log(` ✓ Readiness score: ${readiness.score}/${readiness.maxScore}`);
213
+ console.log(` ✓ Service ready: ${readiness.ready}`);
214
+ console.log(` ✓ Recommendation: ${readiness.recommendation}`);
215
+
216
+ // Show check results
217
+ console.log('\n Check results:');
218
+ for (const [name, check] of Object.entries(readiness.checks)) {
219
+ const status = check.passed ? '✓' : '✗';
220
+ console.log(` ${status} ${name}: ${check.passed ? 'Passed' : 'Failed'}`);
221
+ }
222
+
223
+ server.close();
224
+ console.log('\n ✓ Validator test completed\n');
225
+
226
+ // Test 3: Test Orchestrator (Three-tier testing)
227
+ console.log('3. Testing with TestOrchestrator (three-tier)...\n');
228
+
229
+ const orchestrator = new TestOrchestrator({
230
+ service: app,
231
+ serviceName: 'test-service',
232
+ openApiSpec
233
+ });
234
+
235
+ // Run only unit tests for speed
236
+ console.log(' Running unit tests with full mocks...');
237
+ const unitResults = await orchestrator.runUnitTests();
238
+
239
+ console.log(` ✓ Unit tests passed: ${unitResults.passed}`);
240
+ console.log(` ✓ Coverage: ${unitResults.coverage}%`);
241
+ console.log(` ✓ Tests run: ${unitResults.tests.length}`);
242
+
243
+ // Show individual test results
244
+ console.log('\n Test results:');
245
+ unitResults.tests.forEach(test => {
246
+ const status = test.passed ? '✓' : '✗';
247
+ console.log(` ${status} ${test.name}`);
248
+ });
249
+
250
+ console.log('\n=== All tests completed successfully! ===\n');
251
+ }
252
+
253
+ // Run the tests
254
+ runTests().catch(error => {
255
+ console.error('Test failed:', error);
256
+ process.exit(1);
257
+ });