@onlineapps/service-validator-core 1.0.2

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 (35) hide show
  1. package/README.md +127 -0
  2. package/coverage/clover.xml +468 -0
  3. package/coverage/coverage-final.json +8 -0
  4. package/coverage/lcov-report/base.css +224 -0
  5. package/coverage/lcov-report/block-navigation.js +87 -0
  6. package/coverage/lcov-report/favicon.png +0 -0
  7. package/coverage/lcov-report/index.html +146 -0
  8. package/coverage/lcov-report/prettify.css +1 -0
  9. package/coverage/lcov-report/prettify.js +2 -0
  10. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  11. package/coverage/lcov-report/sorter.js +210 -0
  12. package/coverage/lcov-report/src/index.html +116 -0
  13. package/coverage/lcov-report/src/index.js.html +643 -0
  14. package/coverage/lcov-report/src/security/certificateManager.js.html +799 -0
  15. package/coverage/lcov-report/src/security/index.html +131 -0
  16. package/coverage/lcov-report/src/security/tokenManager.js.html +622 -0
  17. package/coverage/lcov-report/src/validators/connectorValidator.js.html +787 -0
  18. package/coverage/lcov-report/src/validators/endpointValidator.js.html +577 -0
  19. package/coverage/lcov-report/src/validators/healthValidator.js.html +655 -0
  20. package/coverage/lcov-report/src/validators/index.html +161 -0
  21. package/coverage/lcov-report/src/validators/openApiValidator.js.html +517 -0
  22. package/coverage/lcov.info +982 -0
  23. package/jest.config.js +21 -0
  24. package/package.json +31 -0
  25. package/src/index.js +212 -0
  26. package/src/security/ValidationProofVerifier.js +178 -0
  27. package/src/security/certificateManager.js +239 -0
  28. package/src/security/tokenManager.js +194 -0
  29. package/src/validators/connectorValidator.js +235 -0
  30. package/src/validators/endpointValidator.js +165 -0
  31. package/src/validators/healthValidator.js +191 -0
  32. package/src/validators/openApiValidator.js +145 -0
  33. package/test/component/validation-flow.test.js +353 -0
  34. package/test/integration/real-validation.test.js +548 -0
  35. package/test/unit/ValidationCore.test.js +320 -0
@@ -0,0 +1,548 @@
1
+ const ValidationCore = require('../../src/index');
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+
5
+ describe('ValidationCore Integration Tests', () => {
6
+ let validationCore;
7
+
8
+ beforeEach(() => {
9
+ validationCore = new ValidationCore();
10
+ });
11
+
12
+ describe('Real service validation', () => {
13
+ it('should validate a complete service configuration', async () => {
14
+ const serviceData = {
15
+ openApiSpec: {
16
+ openapi: '3.0.0',
17
+ info: {
18
+ title: 'Hello Service API',
19
+ version: '1.0.0',
20
+ description: 'Simple hello/goodbye service'
21
+ },
22
+ servers: [
23
+ { url: 'http://localhost:3000', description: 'Local development' }
24
+ ],
25
+ paths: {
26
+ '/health': {
27
+ get: {
28
+ summary: 'Health check',
29
+ tags: ['Health'],
30
+ responses: {
31
+ '200': {
32
+ description: 'Service is healthy',
33
+ content: {
34
+ 'application/json': {
35
+ schema: {
36
+ type: 'object',
37
+ properties: {
38
+ status: { type: 'string' },
39
+ timestamp: { type: 'string' },
40
+ uptime: { type: 'number' },
41
+ version: { type: 'string' }
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ },
50
+ '/api/hello': {
51
+ post: {
52
+ summary: 'Say hello',
53
+ tags: ['Greetings'],
54
+ requestBody: {
55
+ required: true,
56
+ content: {
57
+ 'application/json': {
58
+ schema: {
59
+ type: 'object',
60
+ required: ['name'],
61
+ properties: {
62
+ name: { type: 'string', minLength: 1 },
63
+ language: {
64
+ type: 'string',
65
+ enum: ['en', 'es', 'fr', 'de'],
66
+ default: 'en'
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ responses: {
74
+ '200': {
75
+ description: 'Greeting response',
76
+ content: {
77
+ 'application/json': {
78
+ schema: {
79
+ type: 'object',
80
+ properties: {
81
+ message: { type: 'string' },
82
+ timestamp: { type: 'string' },
83
+ language: { type: 'string' }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ },
89
+ '400': {
90
+ description: 'Bad request'
91
+ }
92
+ }
93
+ }
94
+ },
95
+ '/api/goodbye': {
96
+ post: {
97
+ summary: 'Say goodbye',
98
+ tags: ['Greetings'],
99
+ requestBody: {
100
+ required: true,
101
+ content: {
102
+ 'application/json': {
103
+ schema: {
104
+ type: 'object',
105
+ required: ['name'],
106
+ properties: {
107
+ name: { type: 'string', minLength: 1 }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ },
113
+ responses: {
114
+ '200': {
115
+ description: 'Goodbye response',
116
+ content: {
117
+ 'application/json': {
118
+ schema: {
119
+ type: 'object',
120
+ properties: {
121
+ message: { type: 'string' },
122
+ timestamp: { type: 'string' }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ },
132
+ components: {
133
+ schemas: {
134
+ Error: {
135
+ type: 'object',
136
+ properties: {
137
+ error: { type: 'string' },
138
+ message: { type: 'string' },
139
+ timestamp: { type: 'string' }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ },
145
+ endpoints: [
146
+ { path: '/health', method: 'GET', handler: 'healthController.check' },
147
+ { path: '/api/hello', method: 'POST', handler: 'greetingController.hello' },
148
+ { path: '/api/goodbye', method: 'POST', handler: 'greetingController.goodbye' }
149
+ ],
150
+ metadata: {
151
+ serviceName: 'hello-service',
152
+ version: '1.0.0',
153
+ description: 'Hello/Goodbye microservice',
154
+ connectors: [
155
+ 'connector-logger',
156
+ 'connector-storage',
157
+ 'connector-registry-client',
158
+ 'connector-mq-client',
159
+ 'connector-cookbook',
160
+ 'connector-hub'
161
+ ],
162
+ dependencies: {
163
+ '@onlineapps/connector-logger': '^1.0.0',
164
+ '@onlineapps/connector-storage': '^1.0.0',
165
+ '@onlineapps/connector-registry-client': '^1.1.0',
166
+ '@onlineapps/connector-mq-client': '^1.1.0',
167
+ '@onlineapps/connector-cookbook': '^1.1.0',
168
+ '@onlineapps/connector-hub': '^1.0.0'
169
+ }
170
+ },
171
+ health: {
172
+ status: 'healthy',
173
+ uptime: 3600,
174
+ version: '1.0.0',
175
+ checks: {
176
+ rabbitmq: 'connected',
177
+ registry: 'registered',
178
+ redis: 'connected'
179
+ },
180
+ metrics: {
181
+ requestsTotal: 150,
182
+ requestsPerMinute: 10,
183
+ errorRate: 0.01,
184
+ avgResponseTime: 45
185
+ }
186
+ }
187
+ };
188
+
189
+ const result = await validationCore.validate(serviceData);
190
+
191
+ expect(result.success).toBe(true);
192
+ expect(result.validated).toBe(true);
193
+ expect(result.errors).toHaveLength(0);
194
+ expect(result.checks.openApi).toBeDefined();
195
+ expect(result.checks.endpoints).toBeDefined();
196
+ expect(result.checks.connectors).toBeDefined();
197
+ expect(result.checks.health).toBeDefined();
198
+ });
199
+
200
+ it('should detect missing required connectors', async () => {
201
+ const serviceData = {
202
+ metadata: {
203
+ serviceName: 'incomplete-service',
204
+ version: '1.0.0',
205
+ connectors: [
206
+ 'connector-logger',
207
+ 'connector-hub'
208
+ // Missing: storage, registry-client, mq-client, cookbook
209
+ ]
210
+ }
211
+ };
212
+
213
+ const result = await validationCore.validate(serviceData);
214
+
215
+ expect(result.validated).toBe(true);
216
+ expect(result.warnings.length).toBeGreaterThan(0);
217
+ // Check for specific missing connectors
218
+ const warningsStr = result.warnings.join(' ');
219
+ expect(warningsStr).toMatch(/storage|registry|mq|cookbook/i);
220
+ });
221
+
222
+ it('should validate in strict mode', async () => {
223
+ const strictCore = new ValidationCore({ strictMode: true });
224
+
225
+ const serviceData = {
226
+ metadata: {
227
+ serviceName: 'strict-service',
228
+ version: '1.0.0',
229
+ connectors: ['connector-logger'] // Missing most required connectors
230
+ }
231
+ };
232
+
233
+ const result = await strictCore.validate(serviceData);
234
+
235
+ expect(result.success).toBe(false); // Should fail in strict mode
236
+ expect(result.errors.length).toBeGreaterThan(0);
237
+ });
238
+ });
239
+
240
+ describe('Token and certificate operations', () => {
241
+ it('should complete full validation-token-certificate flow', async () => {
242
+ const serviceData = {
243
+ metadata: {
244
+ serviceName: 'token-test-service',
245
+ version: '2.0.0',
246
+ connectors: [
247
+ 'connector-logger',
248
+ 'connector-storage',
249
+ 'connector-registry-client',
250
+ 'connector-mq-client',
251
+ 'connector-cookbook'
252
+ ]
253
+ }
254
+ };
255
+
256
+ // Step 1: Validate
257
+ const validationResults = await validationCore.validate(serviceData);
258
+ expect(validationResults.success).toBe(true);
259
+
260
+ // Step 2: Generate pre-validation token
261
+ const tokenData = await validationCore.generatePreValidationToken(
262
+ validationResults,
263
+ serviceData.metadata.serviceName
264
+ );
265
+ expect(tokenData).toBeDefined();
266
+ expect(tokenData.token).toBeDefined();
267
+ expect(typeof tokenData.token).toBe('string');
268
+ expect(tokenData.secret).toBeDefined();
269
+ expect(typeof tokenData.secret).toBe('string');
270
+
271
+ // Step 3: Verify token
272
+ const tokenPayload = await validationCore.verifyPreValidationToken(tokenData.token, tokenData.secret);
273
+ expect(tokenPayload.serviceName).toBe('token-test-service');
274
+ expect(tokenPayload.type).toBe('pre-validation');
275
+
276
+ // Step 4: Generate certificate
277
+ const certificate = await validationCore.generateCertificate(
278
+ validationResults,
279
+ serviceData.metadata.serviceName,
280
+ serviceData.metadata.version
281
+ );
282
+ expect(certificate).toBeDefined();
283
+ expect(certificate.subject.serviceName).toBe('token-test-service');
284
+ expect(certificate.subject.serviceVersion).toBe('2.0.0');
285
+
286
+ // Step 5: Certificate structure validation (verification would fail without proper fingerprint)
287
+ expect(certificate.id).toBeDefined();
288
+ expect(certificate.signature).toBeDefined();
289
+ expect(certificate.issuer).toBeDefined();
290
+ });
291
+
292
+ it('should reject invalid tokens', async () => {
293
+ await expect(
294
+ validationCore.verifyPreValidationToken('invalid.token.here')
295
+ ).rejects.toThrow();
296
+ });
297
+
298
+ it('should reject certificate generation for failed validation', async () => {
299
+ const failedValidation = {
300
+ success: false,
301
+ errors: ['Validation failed'],
302
+ validated: true
303
+ };
304
+
305
+ await expect(
306
+ validationCore.generateCertificate(failedValidation, 'service', '1.0.0')
307
+ ).rejects.toThrow('Cannot generate certificate for failed validation');
308
+ });
309
+ });
310
+
311
+ describe('Functional test generation', () => {
312
+ it('should generate comprehensive tests from OpenAPI spec', async () => {
313
+ const complexSpec = {
314
+ openapi: '3.0.0',
315
+ info: { title: 'Complex API', version: '1.0.0' },
316
+ paths: {
317
+ '/users': {
318
+ get: {
319
+ summary: 'List all users',
320
+ parameters: [
321
+ { name: 'page', in: 'query', schema: { type: 'integer' } },
322
+ { name: 'limit', in: 'query', schema: { type: 'integer' } },
323
+ { name: 'sort', in: 'query', schema: { type: 'string', enum: ['asc', 'desc'] } }
324
+ ],
325
+ responses: {
326
+ '200': { description: 'Success' },
327
+ '401': { description: 'Unauthorized' }
328
+ }
329
+ },
330
+ post: {
331
+ summary: 'Create new user',
332
+ requestBody: {
333
+ required: true,
334
+ content: {
335
+ 'application/json': {
336
+ schema: {
337
+ type: 'object',
338
+ required: ['email', 'password'],
339
+ properties: {
340
+ email: { type: 'string', format: 'email' },
341
+ password: { type: 'string', minLength: 8 },
342
+ name: { type: 'string' }
343
+ }
344
+ }
345
+ }
346
+ }
347
+ },
348
+ responses: {
349
+ '201': { description: 'Created' },
350
+ '400': { description: 'Bad request' },
351
+ '409': { description: 'Conflict' }
352
+ }
353
+ }
354
+ },
355
+ '/users/{id}': {
356
+ parameters: [
357
+ { name: 'id', in: 'path', required: true, schema: { type: 'string' } }
358
+ ],
359
+ get: {
360
+ summary: 'Get user by ID',
361
+ responses: {
362
+ '200': { description: 'Success' },
363
+ '404': { description: 'Not found' }
364
+ }
365
+ },
366
+ put: {
367
+ summary: 'Update user',
368
+ requestBody: {
369
+ required: true,
370
+ content: {
371
+ 'application/json': {
372
+ schema: {
373
+ type: 'object',
374
+ properties: {
375
+ email: { type: 'string', format: 'email' },
376
+ name: { type: 'string' }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ },
382
+ responses: {
383
+ '200': { description: 'Updated' },
384
+ '400': { description: 'Bad request' },
385
+ '404': { description: 'Not found' }
386
+ }
387
+ },
388
+ delete: {
389
+ summary: 'Delete user',
390
+ responses: {
391
+ '204': { description: 'No content' },
392
+ '404': { description: 'Not found' }
393
+ }
394
+ }
395
+ },
396
+ '/users/{id}/avatar': {
397
+ post: {
398
+ summary: 'Upload avatar',
399
+ parameters: [
400
+ { name: 'id', in: 'path', required: true, schema: { type: 'string' } }
401
+ ],
402
+ requestBody: {
403
+ required: true,
404
+ content: {
405
+ 'multipart/form-data': {
406
+ schema: {
407
+ type: 'object',
408
+ properties: {
409
+ file: { type: 'string', format: 'binary' }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ },
415
+ responses: {
416
+ '200': { description: 'Success' }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ };
422
+
423
+ const tests = await validationCore.generateFunctionalTests(complexSpec);
424
+
425
+ expect(tests).toHaveLength(6);
426
+
427
+ // Verify test structure
428
+ tests.forEach(test => {
429
+ expect(test).toHaveProperty('name');
430
+ expect(test).toHaveProperty('method');
431
+ expect(test).toHaveProperty('path');
432
+ expect(test).toHaveProperty('expectedResponses');
433
+ expect(test.expectedResponses.length).toBeGreaterThan(0);
434
+ });
435
+
436
+ // Check specific tests
437
+ const createUserTest = tests.find(t => t.path === '/users' && t.method === 'POST');
438
+ expect(createUserTest).toBeDefined();
439
+ expect(createUserTest.requestBody).toBeDefined();
440
+ expect(createUserTest.expectedSuccessResponse).toBe('201');
441
+
442
+ const uploadAvatarTest = tests.find(t => t.path === '/users/{id}/avatar');
443
+ expect(uploadAvatarTest).toBeDefined();
444
+ expect(uploadAvatarTest.parameters).toHaveLength(1);
445
+
446
+ // Check parameterized paths
447
+ const userByIdTests = tests.filter(t => t.path === '/users/{id}');
448
+ expect(userByIdTests.length).toBe(3); // GET, PUT, DELETE
449
+ });
450
+
451
+ it('should handle edge cases in test generation', async () => {
452
+ const edgeCaseSpec = {
453
+ paths: {
454
+ '/webhook': {
455
+ post: {
456
+ summary: 'Webhook endpoint',
457
+ responses: {
458
+ '200': { description: 'OK' }
459
+ }
460
+ },
461
+ options: { // Should be ignored
462
+ summary: 'OPTIONS request',
463
+ responses: {
464
+ '200': { description: 'OK' }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ };
470
+
471
+ const tests = await validationCore.generateFunctionalTests(edgeCaseSpec);
472
+
473
+ expect(tests).toHaveLength(1); // Only POST, not OPTIONS
474
+ expect(tests[0].method).toBe('POST');
475
+ });
476
+ });
477
+
478
+ describe('Performance tests', () => {
479
+ it('should handle large OpenAPI specs efficiently', async () => {
480
+ const largeSpec = {
481
+ openapi: '3.0.0',
482
+ info: { title: 'Large API', version: '1.0.0' },
483
+ paths: {}
484
+ };
485
+
486
+ // Generate 100 endpoints
487
+ for (let i = 0; i < 100; i++) {
488
+ largeSpec.paths[`/resource${i}`] = {
489
+ get: { summary: `Get resource ${i}`, responses: { '200': { description: 'OK' } } },
490
+ post: { summary: `Create resource ${i}`, responses: { '201': { description: 'Created' } } }
491
+ };
492
+ }
493
+
494
+ const startTime = Date.now();
495
+ const tests = await validationCore.generateFunctionalTests(largeSpec);
496
+ const duration = Date.now() - startTime;
497
+
498
+ expect(tests).toHaveLength(200); // 100 resources * 2 methods
499
+ expect(duration).toBeLessThan(1000); // Should complete within 1 second
500
+ });
501
+
502
+ it('should validate complex service data efficiently', async () => {
503
+ const complexData = {
504
+ openApiSpec: {
505
+ openapi: '3.0.0',
506
+ info: { title: 'Test', version: '1.0.0' },
507
+ paths: {
508
+ '/test': {
509
+ get: { responses: { '200': { description: 'OK' } } }
510
+ }
511
+ }
512
+ },
513
+ endpoints: Array.from({ length: 50 }, (_, i) => ({
514
+ path: `/endpoint${i}`,
515
+ method: 'GET',
516
+ handler: `handler${i}`
517
+ })),
518
+ metadata: {
519
+ serviceName: 'perf-test',
520
+ version: '1.0.0',
521
+ connectors: [
522
+ 'connector-logger',
523
+ 'connector-storage',
524
+ 'connector-registry-client',
525
+ 'connector-mq-client',
526
+ 'connector-cookbook'
527
+ ]
528
+ },
529
+ health: {
530
+ status: 'healthy',
531
+ uptime: 1000000,
532
+ checks: {
533
+ db: 'ok',
534
+ cache: 'ok',
535
+ queue: 'ok'
536
+ }
537
+ }
538
+ };
539
+
540
+ const startTime = Date.now();
541
+ const result = await validationCore.validate(complexData);
542
+ const duration = Date.now() - startTime;
543
+
544
+ expect(result.validated).toBe(true);
545
+ expect(duration).toBeLessThan(500); // Should complete within 500ms
546
+ });
547
+ });
548
+ });