@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.
- package/README.md +78 -0
- package/TESTING_STRATEGY.md +92 -0
- package/docs/DESIGN.md +134 -0
- package/examples/service-wrapper-usage.js +250 -0
- package/examples/three-tier-testing.js +144 -0
- package/jest.config.js +23 -0
- package/onlineapps-conn-e2e-testing-1.0.0.tgz +0 -0
- package/package.json +43 -0
- package/src/CookbookTestRunner.js +434 -0
- package/src/CookbookTestUtils.js +237 -0
- package/src/ServiceReadinessValidator.js +430 -0
- package/src/ServiceTestHarness.js +256 -0
- package/src/ServiceValidator.js +387 -0
- package/src/TestOrchestrator.js +727 -0
- package/src/ValidationOrchestrator.js +506 -0
- package/src/WorkflowTestRunner.js +396 -0
- package/src/helpers/README.md +235 -0
- package/src/helpers/createPreValidationTests.js +321 -0
- package/src/helpers/createServiceReadinessTests.js +245 -0
- package/src/index.js +62 -0
- package/src/mocks/MockMQClient.js +176 -0
- package/src/mocks/MockRegistry.js +164 -0
- package/src/mocks/MockStorage.js +186 -0
- package/src/validators/ServiceStructureValidator.js +487 -0
- package/src/validators/ValidationProofGenerator.js +79 -0
- package/test-mq-flow.js +72 -0
- package/test-orchestrator.js +95 -0
- package/tests/component/testing-framework-integration.test.js +313 -0
- package/tests/integration/ServiceReadiness.test.js +265 -0
- package/tests/monitoring-e2e.test.js +315 -0
- package/tests/run-example.js +257 -0
- package/tests/unit/CookbookTestRunner.test.js +353 -0
- package/tests/unit/MockMQClient.test.js +190 -0
- package/tests/unit/MockRegistry.test.js +233 -0
- package/tests/unit/MockStorage.test.js +257 -0
- package/tests/unit/ServiceValidator.test.js +429 -0
- package/tests/unit/WorkflowTestRunner.test.js +546 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Quick test of ValidationOrchestrator
|
|
6
|
+
*
|
|
7
|
+
* Usage: node test-orchestrator.js <serviceRoot>
|
|
8
|
+
* Example: node test-orchestrator.js /var/www/html/oa_drive/services/hello-service
|
|
9
|
+
*
|
|
10
|
+
* Loads service configuration from conn-config/config.json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const ValidationOrchestrator = require('./src/ValidationOrchestrator');
|
|
16
|
+
|
|
17
|
+
async function test() {
|
|
18
|
+
// Get service root from CLI or default to hello-service
|
|
19
|
+
const serviceRoot = process.argv[2] || path.resolve(__dirname, '../../../services/hello-service');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(serviceRoot)) {
|
|
22
|
+
console.error(`โ Service root not found: ${serviceRoot}`);
|
|
23
|
+
console.error('Usage: node test-orchestrator.js <serviceRoot>');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Load service configuration
|
|
28
|
+
const configPath = path.join(serviceRoot, 'conn-config', 'config.json');
|
|
29
|
+
if (!fs.existsSync(configPath)) {
|
|
30
|
+
console.error(`โ Config not found: ${configPath}`);
|
|
31
|
+
console.error('Service must have conn-config/config.json');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
36
|
+
const serviceName = config.service?.name;
|
|
37
|
+
const serviceVersion = config.service?.version;
|
|
38
|
+
|
|
39
|
+
if (!serviceName || !serviceVersion) {
|
|
40
|
+
console.error('โ Config must contain service.name and service.version');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`๐งช Testing ValidationOrchestrator`);
|
|
45
|
+
console.log(` Service: ${serviceName} v${serviceVersion}`);
|
|
46
|
+
console.log(` Root: ${serviceRoot}\n`);
|
|
47
|
+
|
|
48
|
+
const orchestrator = new ValidationOrchestrator({
|
|
49
|
+
serviceRoot,
|
|
50
|
+
serviceName,
|
|
51
|
+
serviceVersion,
|
|
52
|
+
logger: console
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const result = await orchestrator.validate();
|
|
57
|
+
|
|
58
|
+
console.log('\n๐ Validation Results:');
|
|
59
|
+
console.log('Success:', result.success);
|
|
60
|
+
console.log('Duration:', result.durationMs, 'ms');
|
|
61
|
+
|
|
62
|
+
if (result.skipped) {
|
|
63
|
+
console.log('โ Used existing proof (validation skipped)');
|
|
64
|
+
console.log('Fingerprint:', result.proof?.fingerprint);
|
|
65
|
+
} else {
|
|
66
|
+
console.log('Tests run:', result.totalTests);
|
|
67
|
+
console.log('Tests passed:', result.passedTests);
|
|
68
|
+
console.log('Tests failed:', result.failedTests);
|
|
69
|
+
|
|
70
|
+
if (result.errors && result.errors.length > 0) {
|
|
71
|
+
console.log('\nโ Errors:');
|
|
72
|
+
result.errors.forEach(err => console.log(' -', err));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
76
|
+
console.log('\nโ ๏ธ Warnings:');
|
|
77
|
+
result.warnings.forEach(warn => console.log(' -', warn));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (result.proof) {
|
|
81
|
+
console.log('\nโ
Proof generated:');
|
|
82
|
+
console.log(' Fingerprint:', result.fingerprint);
|
|
83
|
+
console.log(' Location: conn-runtime/validation-proof.json');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.exit(result.success ? 0 : 1);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('\nโ Test failed:', error.message);
|
|
90
|
+
console.error(error.stack);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
test();
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component tests for E2E Testing Framework
|
|
5
|
+
* Tests the integration between different testing utilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
describe('E2E Testing Framework Integration @component', () => {
|
|
9
|
+
let framework;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
delete require.cache[require.resolve('../../src/index.js')];
|
|
14
|
+
framework = require('../../src/index.js');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Framework Exports', () => {
|
|
18
|
+
it('should export all mock components', () => {
|
|
19
|
+
expect(framework.MockMQClient).toBeDefined();
|
|
20
|
+
expect(framework.MockRegistry).toBeDefined();
|
|
21
|
+
expect(framework.MockStorage).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should export all test utilities', () => {
|
|
25
|
+
expect(framework.ServiceTestHarness).toBeDefined();
|
|
26
|
+
expect(framework.ServiceValidator).toBeDefined();
|
|
27
|
+
expect(framework.WorkflowTestRunner).toBeDefined();
|
|
28
|
+
expect(framework.CookbookTestUtils).toBeDefined();
|
|
29
|
+
expect(framework.ServiceReadinessValidator).toBeDefined();
|
|
30
|
+
expect(framework.TestOrchestrator).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should export factory functions', () => {
|
|
34
|
+
expect(typeof framework.createTestHarness).toBe('function');
|
|
35
|
+
expect(typeof framework.createValidator).toBe('function');
|
|
36
|
+
expect(typeof framework.createMockMQ).toBe('function');
|
|
37
|
+
expect(typeof framework.createMockRegistry).toBe('function');
|
|
38
|
+
expect(typeof framework.createMockStorage).toBe('function');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('Factory Functions', () => {
|
|
43
|
+
it('should create MockMQClient instance', () => {
|
|
44
|
+
const mq = framework.createMockMQ();
|
|
45
|
+
expect(mq.constructor.name).toBe('MockMQClient');
|
|
46
|
+
expect(typeof mq.connect).toBe('function');
|
|
47
|
+
expect(typeof mq.publish).toBe('function');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should create MockRegistry instance', () => {
|
|
51
|
+
const registry = framework.createMockRegistry();
|
|
52
|
+
expect(registry.constructor.name).toBe('MockRegistry');
|
|
53
|
+
expect(typeof registry.register).toBe('function');
|
|
54
|
+
expect(typeof registry.discover).toBe('function');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should create MockStorage instance', () => {
|
|
58
|
+
const storage = framework.createMockStorage();
|
|
59
|
+
expect(storage.constructor.name).toBe('MockStorage');
|
|
60
|
+
expect(typeof storage.upload).toBe('function');
|
|
61
|
+
expect(typeof storage.download).toBe('function');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should create ServiceTestHarness with options', () => {
|
|
65
|
+
const harness = framework.createTestHarness({
|
|
66
|
+
serviceName: 'test-service'
|
|
67
|
+
});
|
|
68
|
+
expect(harness.constructor.name).toBe('ServiceTestHarness');
|
|
69
|
+
expect(harness.serviceName).toBe('test-service');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should create ServiceValidator with options', () => {
|
|
73
|
+
const validator = framework.createValidator({
|
|
74
|
+
services: ['service1', 'service2']
|
|
75
|
+
});
|
|
76
|
+
expect(validator.constructor.name).toBe('ServiceValidator');
|
|
77
|
+
expect(validator.services).toEqual(['service1', 'service2']);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Component Integration', () => {
|
|
82
|
+
it('should allow ServiceTestHarness to use mocks', async () => {
|
|
83
|
+
const harness = new framework.ServiceTestHarness({
|
|
84
|
+
serviceName: 'integration-test'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await harness.setup();
|
|
88
|
+
|
|
89
|
+
// Harness should have created mock instances
|
|
90
|
+
expect(harness.mq).toBeDefined();
|
|
91
|
+
expect(harness.registry).toBeDefined();
|
|
92
|
+
expect(harness.storage).toBeDefined();
|
|
93
|
+
|
|
94
|
+
await harness.cleanup();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should allow WorkflowTestRunner to use MockMQClient', async () => {
|
|
98
|
+
const mq = new framework.MockMQClient();
|
|
99
|
+
const runner = new framework.WorkflowTestRunner({
|
|
100
|
+
mq: mq
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(runner.mq).toBe(mq);
|
|
104
|
+
expect(runner.state).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should allow ServiceValidator to work with OpenAPI specs', () => {
|
|
108
|
+
const validator = new framework.ServiceValidator();
|
|
109
|
+
|
|
110
|
+
const spec = {
|
|
111
|
+
openapi: '3.0.0',
|
|
112
|
+
paths: {
|
|
113
|
+
'/test': {
|
|
114
|
+
get: {
|
|
115
|
+
responses: {
|
|
116
|
+
'200': { description: 'Success' }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const paths = validator.countEndpoints(spec.paths);
|
|
124
|
+
expect(paths).toBeGreaterThan(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should allow CookbookTestUtils to generate valid cookbooks', () => {
|
|
128
|
+
const utils = new framework.CookbookTestUtils();
|
|
129
|
+
|
|
130
|
+
const cookbook = utils.generateCookbook({
|
|
131
|
+
name: 'test-cookbook',
|
|
132
|
+
steps: 3
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(cookbook.name).toBe('test-cookbook');
|
|
136
|
+
expect(cookbook.version).toBeDefined();
|
|
137
|
+
expect(cookbook.steps).toHaveLength(3);
|
|
138
|
+
expect(cookbook.steps[0]).toHaveProperty('service');
|
|
139
|
+
expect(cookbook.steps[0]).toHaveProperty('operation');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should allow TestOrchestrator to coordinate components', () => {
|
|
143
|
+
const harness = new framework.ServiceTestHarness({
|
|
144
|
+
serviceName: 'orchestrated-test'
|
|
145
|
+
});
|
|
146
|
+
const validator = new framework.ServiceValidator();
|
|
147
|
+
const runner = new framework.WorkflowTestRunner({
|
|
148
|
+
mq: new framework.MockMQClient()
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const orchestrator = new framework.TestOrchestrator({
|
|
152
|
+
harness,
|
|
153
|
+
validator,
|
|
154
|
+
runner
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(orchestrator.harness).toBe(harness);
|
|
158
|
+
expect(orchestrator.validator).toBe(validator);
|
|
159
|
+
expect(orchestrator.runner).toBe(runner);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Mock Components Behavior', () => {
|
|
164
|
+
it('MockMQClient should handle basic operations', async () => {
|
|
165
|
+
const mq = new framework.MockMQClient();
|
|
166
|
+
|
|
167
|
+
// Should connect
|
|
168
|
+
await mq.connect();
|
|
169
|
+
expect(mq.isConnected).toBe(true);
|
|
170
|
+
|
|
171
|
+
// Should publish messages
|
|
172
|
+
await mq.publish('test-queue', { test: true });
|
|
173
|
+
const messages = mq.getMessages('test-queue');
|
|
174
|
+
expect(messages).toHaveLength(1);
|
|
175
|
+
expect(messages[0]).toEqual({ test: true });
|
|
176
|
+
|
|
177
|
+
// Should disconnect
|
|
178
|
+
await mq.disconnect();
|
|
179
|
+
expect(mq.isConnected).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('MockRegistry should handle service registration', async () => {
|
|
183
|
+
const registry = new framework.MockRegistry();
|
|
184
|
+
|
|
185
|
+
// Should register service
|
|
186
|
+
await registry.register({
|
|
187
|
+
name: 'test-service',
|
|
188
|
+
version: '1.0.0'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Should discover service
|
|
192
|
+
const services = await registry.discover('test-service');
|
|
193
|
+
expect(services).toHaveLength(1);
|
|
194
|
+
expect(services[0].name).toBe('test-service');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('MockStorage should handle file operations', async () => {
|
|
198
|
+
const storage = new framework.MockStorage();
|
|
199
|
+
|
|
200
|
+
// Should upload file
|
|
201
|
+
const data = Buffer.from('test data');
|
|
202
|
+
await storage.upload('bucket', 'key', data);
|
|
203
|
+
|
|
204
|
+
// Should download file
|
|
205
|
+
const retrieved = await storage.download('bucket', 'key');
|
|
206
|
+
expect(retrieved.toString()).toBe('test data');
|
|
207
|
+
|
|
208
|
+
// Should list files
|
|
209
|
+
const files = await storage.list('bucket');
|
|
210
|
+
expect(files).toContain('key');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('Testing Utilities Behavior', () => {
|
|
215
|
+
it('ServiceReadinessValidator should check readiness', async () => {
|
|
216
|
+
const validator = new framework.ServiceReadinessValidator({
|
|
217
|
+
services: ['api', 'worker']
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Mock service readiness
|
|
221
|
+
validator.mockServiceReady('api');
|
|
222
|
+
validator.mockServiceReady('worker');
|
|
223
|
+
|
|
224
|
+
const allReady = await validator.checkAll();
|
|
225
|
+
expect(allReady).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('CookbookTestUtils should validate cookbook structure', () => {
|
|
229
|
+
const utils = new framework.CookbookTestUtils();
|
|
230
|
+
|
|
231
|
+
const validCookbook = {
|
|
232
|
+
name: 'valid-cookbook',
|
|
233
|
+
version: '1.0.0',
|
|
234
|
+
steps: [
|
|
235
|
+
{
|
|
236
|
+
service: 'service1',
|
|
237
|
+
operation: 'process'
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const validation = utils.validate(validCookbook);
|
|
243
|
+
expect(validation.valid).toBe(true);
|
|
244
|
+
|
|
245
|
+
const invalidCookbook = { steps: [] };
|
|
246
|
+
const invalidValidation = utils.validate(invalidCookbook);
|
|
247
|
+
expect(invalidValidation.valid).toBe(false);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('Error Handling', () => {
|
|
252
|
+
it('should handle missing configuration gracefully', () => {
|
|
253
|
+
expect(() => new framework.ServiceTestHarness()).not.toThrow();
|
|
254
|
+
expect(() => new framework.ServiceValidator()).not.toThrow();
|
|
255
|
+
expect(() => new framework.WorkflowTestRunner()).not.toThrow();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should handle invalid operations gracefully', async () => {
|
|
259
|
+
const mq = new framework.MockMQClient();
|
|
260
|
+
|
|
261
|
+
// Should fail when not connected
|
|
262
|
+
await expect(mq.publish('queue', {}))
|
|
263
|
+
.rejects.toThrow('Not connected');
|
|
264
|
+
|
|
265
|
+
// Storage should fail on non-existent files
|
|
266
|
+
const storage = new framework.MockStorage();
|
|
267
|
+
await expect(storage.download('bucket', 'nonexistent'))
|
|
268
|
+
.rejects.toThrow();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Complete Testing Workflow', () => {
|
|
273
|
+
it('should support complete test workflow', async () => {
|
|
274
|
+
// 1. Setup test environment
|
|
275
|
+
const harness = new framework.ServiceTestHarness({
|
|
276
|
+
serviceName: 'complete-test'
|
|
277
|
+
});
|
|
278
|
+
await harness.setup();
|
|
279
|
+
|
|
280
|
+
// 2. Register mock services
|
|
281
|
+
await harness.registry.register({
|
|
282
|
+
name: 'service1',
|
|
283
|
+
version: '1.0.0'
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// 3. Create test cookbook
|
|
287
|
+
const utils = new framework.CookbookTestUtils();
|
|
288
|
+
const cookbook = utils.generateCookbook({
|
|
289
|
+
name: 'test-workflow',
|
|
290
|
+
steps: 2
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// 4. Run workflow
|
|
294
|
+
const runner = new framework.WorkflowTestRunner({
|
|
295
|
+
mq: harness.mq
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Mock step execution
|
|
299
|
+
runner.mockStepResponse('test-service-0', { success: true });
|
|
300
|
+
runner.mockStepResponse('test-service-1', { complete: true });
|
|
301
|
+
|
|
302
|
+
const result = await runner.runWorkflow(cookbook, { input: 'data' });
|
|
303
|
+
expect(result.status).toBe('completed');
|
|
304
|
+
|
|
305
|
+
// 5. Validate results
|
|
306
|
+
const validator = new framework.ServiceValidator();
|
|
307
|
+
expect(validator).toBeDefined();
|
|
308
|
+
|
|
309
|
+
// 6. Cleanup
|
|
310
|
+
await harness.cleanup();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Integration test for complete service readiness validation
|
|
5
|
+
* Uses existing connector tests, doesn't duplicate them
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { ServiceTestHarness, ServiceValidator, WorkflowTestRunner } = require('../../src');
|
|
9
|
+
const express = require('express');
|
|
10
|
+
|
|
11
|
+
describe('Service Readiness Integration @integration', () => {
|
|
12
|
+
let harness;
|
|
13
|
+
let app;
|
|
14
|
+
let openApiSpec;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Create minimal test service
|
|
18
|
+
app = express();
|
|
19
|
+
app.use(express.json());
|
|
20
|
+
|
|
21
|
+
app.get('/health', (req, res) => {
|
|
22
|
+
res.json({ status: 'healthy' });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
app.post('/process', (req, res) => {
|
|
26
|
+
res.json({ processed: true, data: req.body });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
openApiSpec = {
|
|
30
|
+
openapi: '3.0.0',
|
|
31
|
+
info: { title: 'Test Service', version: '1.0.0' },
|
|
32
|
+
paths: {
|
|
33
|
+
'/health': {
|
|
34
|
+
get: {
|
|
35
|
+
operationId: 'healthCheck',
|
|
36
|
+
responses: {
|
|
37
|
+
'200': {
|
|
38
|
+
content: {
|
|
39
|
+
'application/json': {
|
|
40
|
+
schema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
status: { type: 'string' }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
'/process': {
|
|
53
|
+
post: {
|
|
54
|
+
operationId: 'processData',
|
|
55
|
+
requestBody: {
|
|
56
|
+
content: {
|
|
57
|
+
'application/json': {
|
|
58
|
+
schema: { type: 'object' }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
responses: {
|
|
63
|
+
'200': {
|
|
64
|
+
content: {
|
|
65
|
+
'application/json': {
|
|
66
|
+
schema: { type: 'object' }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(async () => {
|
|
78
|
+
if (harness) {
|
|
79
|
+
await harness.stop();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('Complete Service Validation', () => {
|
|
84
|
+
it('should validate service is ready for production', async () => {
|
|
85
|
+
harness = new ServiceTestHarness({
|
|
86
|
+
service: app,
|
|
87
|
+
serviceName: 'test-service',
|
|
88
|
+
openApiSpec,
|
|
89
|
+
mockInfrastructure: true
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await harness.start();
|
|
93
|
+
|
|
94
|
+
// 1. Verify service is registered
|
|
95
|
+
const registration = harness.getServiceRegistration();
|
|
96
|
+
expect(registration).toBeDefined();
|
|
97
|
+
expect(registration.name).toBe('test-service');
|
|
98
|
+
expect(registration.status).toBe('active');
|
|
99
|
+
|
|
100
|
+
// 2. Validate OpenAPI compliance
|
|
101
|
+
const validator = new ServiceValidator();
|
|
102
|
+
const validation = await validator.validateService(
|
|
103
|
+
harness.baseUrl,
|
|
104
|
+
openApiSpec
|
|
105
|
+
);
|
|
106
|
+
expect(validation.valid).toBe(true);
|
|
107
|
+
expect(validation.coverage).toBe(100);
|
|
108
|
+
|
|
109
|
+
// 3. Test workflow execution
|
|
110
|
+
const workflow = await harness.simulateWorkflow({
|
|
111
|
+
steps: [
|
|
112
|
+
{
|
|
113
|
+
id: 'step1',
|
|
114
|
+
type: 'task',
|
|
115
|
+
service: 'test-service',
|
|
116
|
+
operation: 'processData',
|
|
117
|
+
input: { test: 'data' }
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
expect(workflow.completed).toBe(true);
|
|
122
|
+
|
|
123
|
+
// 4. Verify message queue integration
|
|
124
|
+
const messages = harness.getPublishedMessages('workflow.completed');
|
|
125
|
+
expect(messages.length).toBeGreaterThan(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should detect service not ready - missing endpoints', async () => {
|
|
129
|
+
// Remove an endpoint
|
|
130
|
+
delete openApiSpec.paths['/process'];
|
|
131
|
+
|
|
132
|
+
harness = new ServiceTestHarness({
|
|
133
|
+
service: app,
|
|
134
|
+
serviceName: 'test-service',
|
|
135
|
+
openApiSpec,
|
|
136
|
+
mockInfrastructure: true
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await harness.start();
|
|
140
|
+
|
|
141
|
+
const validator = new ServiceValidator();
|
|
142
|
+
const validation = await validator.validateService(
|
|
143
|
+
harness.baseUrl,
|
|
144
|
+
openApiSpec
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Service still works but coverage is reduced
|
|
148
|
+
expect(validation.valid).toBe(true);
|
|
149
|
+
expect(validation.coverage).toBeLessThan(100);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Cookbook Validation for Production', () => {
|
|
154
|
+
it('should validate cookbook can execute in environment', async () => {
|
|
155
|
+
harness = new ServiceTestHarness({
|
|
156
|
+
service: app,
|
|
157
|
+
serviceName: 'test-service',
|
|
158
|
+
openApiSpec,
|
|
159
|
+
mockInfrastructure: true
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await harness.start();
|
|
163
|
+
|
|
164
|
+
const cookbook = {
|
|
165
|
+
version: '1.0.0',
|
|
166
|
+
steps: [
|
|
167
|
+
{
|
|
168
|
+
id: 'validate',
|
|
169
|
+
type: 'task',
|
|
170
|
+
service: 'test-service',
|
|
171
|
+
operation: 'healthCheck'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'process',
|
|
175
|
+
type: 'task',
|
|
176
|
+
service: 'test-service',
|
|
177
|
+
operation: 'processData',
|
|
178
|
+
input: { data: '$steps.validate' }
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Validate cookbook structure
|
|
184
|
+
const validator = new ServiceValidator();
|
|
185
|
+
const cookbookValidation = await validator.validateCookbook(
|
|
186
|
+
cookbook,
|
|
187
|
+
harness.registry
|
|
188
|
+
);
|
|
189
|
+
expect(cookbookValidation.valid).toBe(true);
|
|
190
|
+
|
|
191
|
+
// Execute cookbook
|
|
192
|
+
const runner = new WorkflowTestRunner({
|
|
193
|
+
mqClient: harness.mqClient,
|
|
194
|
+
registry: harness.registry,
|
|
195
|
+
storage: harness.storage
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const workflow = await runner.runWorkflow(cookbook);
|
|
199
|
+
expect(workflow.status).toBe('completed');
|
|
200
|
+
expect(workflow.results).toHaveLength(2);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('Production Deployment Readiness', () => {
|
|
205
|
+
it('should verify all production requirements', async () => {
|
|
206
|
+
harness = new ServiceTestHarness({
|
|
207
|
+
service: app,
|
|
208
|
+
serviceName: 'test-service',
|
|
209
|
+
openApiSpec,
|
|
210
|
+
mockInfrastructure: true
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await harness.start();
|
|
214
|
+
|
|
215
|
+
const readinessChecks = {
|
|
216
|
+
serviceRunning: false,
|
|
217
|
+
apiAccessible: false,
|
|
218
|
+
registryConnected: false,
|
|
219
|
+
mqConnected: false,
|
|
220
|
+
openApiValid: false,
|
|
221
|
+
healthCheckPassing: false
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Check service is running
|
|
225
|
+
readinessChecks.serviceRunning = harness.isRunning;
|
|
226
|
+
|
|
227
|
+
// Check API is accessible
|
|
228
|
+
try {
|
|
229
|
+
const response = await harness.callApi('GET', '/health');
|
|
230
|
+
readinessChecks.apiAccessible = true;
|
|
231
|
+
readinessChecks.healthCheckPassing = response.status === 'healthy';
|
|
232
|
+
} catch (e) {
|
|
233
|
+
// API not accessible
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Check registry connection
|
|
237
|
+
readinessChecks.registryConnected = !!harness.getServiceRegistration();
|
|
238
|
+
|
|
239
|
+
// Check MQ connection
|
|
240
|
+
readinessChecks.mqConnected = harness.mqClient.isConnected;
|
|
241
|
+
|
|
242
|
+
// Validate OpenAPI
|
|
243
|
+
const validator = new ServiceValidator();
|
|
244
|
+
const validation = await validator.validateService(
|
|
245
|
+
harness.baseUrl,
|
|
246
|
+
openApiSpec
|
|
247
|
+
);
|
|
248
|
+
readinessChecks.openApiValid = validation.valid;
|
|
249
|
+
|
|
250
|
+
// All checks should pass
|
|
251
|
+
expect(readinessChecks).toEqual({
|
|
252
|
+
serviceRunning: true,
|
|
253
|
+
apiAccessible: true,
|
|
254
|
+
registryConnected: true,
|
|
255
|
+
mqConnected: true,
|
|
256
|
+
openApiValid: true,
|
|
257
|
+
healthCheckPassing: true
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Get statistics
|
|
261
|
+
const stats = harness.getStats();
|
|
262
|
+
expect(stats).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
});
|