pumuki 6.3.294 → 6.3.295

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.
@@ -7,10 +7,17 @@ import {
7
7
  findDtoNestedPropertyWithoutNestedValidationMatch,
8
8
  findBackendControllerRouteWithoutGuardLines,
9
9
  findBackendControllerRouteWithoutRolesLines,
10
+ findBackendEventHandlerWithoutOnEventLines,
11
+ findBackendAuthRouteWithoutThrottleLines,
10
12
  findBackendPermissiveCorsConfigurationLines,
11
13
  findBackendStringLiteralUnionEnumCandidateLines,
14
+ findBackendSensitiveCacheWriteLines,
12
15
  findBackendHardDeleteWithoutSoftDeleteLines,
13
16
  findBackendLogWithoutContextLines,
17
+ findBackendAuthResponseWithoutRefreshTokenLines,
18
+ findBackendProcessEnvDefaultFallbackLines,
19
+ findBackendDirectProcessEnvReadLines,
20
+ findBackendConfigModuleWithoutValidationLines,
14
21
  findBackendInconsistentErrorResponseLines,
15
22
  findBackendErrorPayloadWithSuccessStatusLines,
16
23
  findBackendControllerEntityResponseLines,
@@ -19,6 +26,7 @@ import {
19
26
  findBackendRawThrowExpressionLines,
20
27
  findBackendMissingGlobalValidationPipeLines,
21
28
  findBackendMissingHelmetSecurityHeadersLines,
29
+ findBackendMissingCompressionMiddlewareLines,
22
30
  findFrameworkDependencyImportMatch,
23
31
  findMixedCommandQueryClassMatch,
24
32
  findMixedCommandQueryInterfaceMatch,
@@ -27,6 +35,7 @@ import {
27
35
  findNetworkCallWithoutErrorHandlingLines,
28
36
  findNestJsConstructorDependencyWithoutDecoratorMatch,
29
37
  findPersistenceMutationWithoutAuditEventMatch,
38
+ findDtoPropertyWithoutApiPropertyLines,
30
39
  findRecordStringUnknownTypeLines,
31
40
  findUndefinedInBaseTypeUnionLines,
32
41
  findUnknownWithoutGuardLines,
@@ -40,11 +49,15 @@ import {
40
49
  hasDefaultExportedApiRouteHandler,
41
50
  hasDirectNetworkCall,
42
51
  hasDtoPropertyWithoutValidation,
52
+ hasDtoPropertyWithoutApiProperty,
43
53
  hasDtoNestedPropertyWithoutNestedValidation,
44
54
  hasBackendControllerRouteWithoutGuard,
45
55
  hasBackendControllerRouteWithoutRoles,
56
+ hasBackendEventHandlerWithoutOnEvent,
57
+ hasBackendAuthRouteWithoutThrottle,
46
58
  hasBackendPermissiveCorsConfiguration,
47
59
  hasBackendStringLiteralUnionEnumCandidate,
60
+ hasBackendSensitiveCacheWrite,
48
61
  hasBackendHardDeleteWithoutSoftDelete,
49
62
  hasBackendInconsistentErrorResponse,
50
63
  hasBackendErrorPayloadWithSuccessStatus,
@@ -54,6 +67,7 @@ import {
54
67
  hasBackendRawThrowExpression,
55
68
  hasBackendMissingGlobalValidationPipe,
56
69
  hasBackendMissingHelmetSecurityHeaders,
70
+ hasBackendMissingCompressionMiddleware,
57
71
  hasEmptyCatchClause,
58
72
  hasEvalCall,
59
73
  hasExplicitAnyType,
@@ -64,6 +78,10 @@ import {
64
78
  hasNestedIfElseStatement,
65
79
  hasNestJsConstructorDependencyWithoutDecorator,
66
80
  hasBackendLogWithoutContext,
81
+ hasBackendAuthResponseWithoutRefreshToken,
82
+ hasBackendProcessEnvDefaultFallback,
83
+ hasBackendDirectProcessEnvRead,
84
+ hasBackendConfigModuleWithoutValidation,
67
85
  hasBackendMagicNumberLiteral,
68
86
  hasBackendGenericErrorThrow,
69
87
  hasBackendProductionMockOrSpy,
@@ -317,6 +335,237 @@ test('hasBackendLogWithoutContext detecta logs backend sin request user o trace
317
335
  assert.equal(hasBackendLogWithoutContext(contextualLoggerAst), false);
318
336
  });
319
337
 
338
+ test('hasBackendAuthResponseWithoutRefreshToken detecta respuestas auth solo con access token', () => {
339
+ const accessOnlyResponseAst = {
340
+ type: 'Program',
341
+ body: [
342
+ {
343
+ type: 'ReturnStatement',
344
+ argument: {
345
+ type: 'ObjectExpression',
346
+ loc: { start: { line: 16 }, end: { line: 19 } },
347
+ properties: [
348
+ {
349
+ type: 'ObjectProperty',
350
+ key: { type: 'Identifier', name: 'accessToken' },
351
+ value: { type: 'Identifier', name: 'accessToken' },
352
+ },
353
+ ],
354
+ },
355
+ },
356
+ ],
357
+ };
358
+ const refreshTokenResponseAst = {
359
+ type: 'Program',
360
+ body: [
361
+ {
362
+ type: 'ReturnStatement',
363
+ argument: {
364
+ type: 'ObjectExpression',
365
+ loc: { start: { line: 27 }, end: { line: 31 } },
366
+ properties: [
367
+ {
368
+ type: 'ObjectProperty',
369
+ key: { type: 'Identifier', name: 'accessToken' },
370
+ value: { type: 'Identifier', name: 'accessToken' },
371
+ },
372
+ {
373
+ type: 'ObjectProperty',
374
+ key: { type: 'Identifier', name: 'refreshToken' },
375
+ value: { type: 'Identifier', name: 'refreshToken' },
376
+ },
377
+ ],
378
+ },
379
+ },
380
+ ],
381
+ };
382
+
383
+ assert.equal(hasBackendAuthResponseWithoutRefreshToken(accessOnlyResponseAst), true);
384
+ assert.deepEqual(findBackendAuthResponseWithoutRefreshTokenLines(accessOnlyResponseAst), [16]);
385
+ assert.equal(hasBackendAuthResponseWithoutRefreshToken(refreshTokenResponseAst), false);
386
+ });
387
+
388
+ test('hasBackendProcessEnvDefaultFallback detecta defaults literales en process.env', () => {
389
+ const envFallbackAst = {
390
+ type: 'Program',
391
+ body: [
392
+ {
393
+ type: 'VariableDeclaration',
394
+ declarations: [
395
+ {
396
+ type: 'VariableDeclarator',
397
+ id: { type: 'Identifier', name: 'jwtSecret' },
398
+ init: {
399
+ type: 'LogicalExpression',
400
+ operator: '||',
401
+ loc: { start: { line: 6 }, end: { line: 6 } },
402
+ left: {
403
+ type: 'MemberExpression',
404
+ object: {
405
+ type: 'MemberExpression',
406
+ object: { type: 'Identifier', name: 'process' },
407
+ property: { type: 'Identifier', name: 'env' },
408
+ },
409
+ property: { type: 'Identifier', name: 'JWT_SECRET' },
410
+ },
411
+ right: { type: 'StringLiteral', value: 'dev-secret' },
412
+ },
413
+ },
414
+ {
415
+ type: 'VariableDeclarator',
416
+ id: { type: 'Identifier', name: 'port' },
417
+ init: {
418
+ type: 'LogicalExpression',
419
+ operator: '??',
420
+ loc: { start: { line: 9 }, end: { line: 9 } },
421
+ left: {
422
+ type: 'MemberExpression',
423
+ object: {
424
+ type: 'MemberExpression',
425
+ object: { type: 'Identifier', name: 'process' },
426
+ property: { type: 'Identifier', name: 'env' },
427
+ },
428
+ property: { type: 'Identifier', name: 'PORT' },
429
+ },
430
+ right: { type: 'NumericLiteral', value: 3000 },
431
+ },
432
+ },
433
+ ],
434
+ },
435
+ ],
436
+ };
437
+ const strictEnvAst = {
438
+ type: 'Program',
439
+ body: [
440
+ {
441
+ type: 'VariableDeclaration',
442
+ declarations: [
443
+ {
444
+ type: 'VariableDeclarator',
445
+ id: { type: 'Identifier', name: 'jwtSecret' },
446
+ init: {
447
+ type: 'CallExpression',
448
+ callee: { type: 'Identifier', name: 'requiredEnv' },
449
+ arguments: [
450
+ {
451
+ type: 'MemberExpression',
452
+ object: {
453
+ type: 'MemberExpression',
454
+ object: { type: 'Identifier', name: 'process' },
455
+ property: { type: 'Identifier', name: 'env' },
456
+ },
457
+ property: { type: 'Identifier', name: 'JWT_SECRET' },
458
+ },
459
+ ],
460
+ },
461
+ },
462
+ ],
463
+ },
464
+ ],
465
+ };
466
+
467
+ assert.equal(hasBackendProcessEnvDefaultFallback(envFallbackAst), true);
468
+ assert.deepEqual(findBackendProcessEnvDefaultFallbackLines(envFallbackAst), [6, 9]);
469
+ assert.equal(hasBackendProcessEnvDefaultFallback(strictEnvAst), false);
470
+ });
471
+
472
+ test('hasBackendDirectProcessEnvRead detecta lecturas directas de process.env', () => {
473
+ const directEnvAst = {
474
+ type: 'Program',
475
+ body: [
476
+ {
477
+ type: 'VariableDeclaration',
478
+ declarations: [
479
+ {
480
+ type: 'VariableDeclarator',
481
+ id: { type: 'Identifier', name: 'databaseUrl' },
482
+ init: {
483
+ type: 'MemberExpression',
484
+ loc: { start: { line: 6 }, end: { line: 6 } },
485
+ object: {
486
+ type: 'MemberExpression',
487
+ object: { type: 'Identifier', name: 'process' },
488
+ property: { type: 'Identifier', name: 'env' },
489
+ },
490
+ property: { type: 'Identifier', name: 'DATABASE_URL' },
491
+ },
492
+ },
493
+ ],
494
+ },
495
+ ],
496
+ };
497
+ const configServiceAst = {
498
+ type: 'CallExpression',
499
+ callee: {
500
+ type: 'MemberExpression',
501
+ object: { type: 'Identifier', name: 'configService' },
502
+ property: { type: 'Identifier', name: 'getOrThrow' },
503
+ },
504
+ arguments: [{ type: 'StringLiteral', value: 'DATABASE_URL' }],
505
+ };
506
+
507
+ assert.equal(hasBackendDirectProcessEnvRead(directEnvAst), true);
508
+ assert.deepEqual(findBackendDirectProcessEnvReadLines(directEnvAst), [6]);
509
+ assert.equal(hasBackendDirectProcessEnvRead(configServiceAst), false);
510
+ });
511
+
512
+ test('hasBackendConfigModuleWithoutValidation detecta ConfigModule sin validacion de env', () => {
513
+ const missingValidationAst = {
514
+ type: 'Program',
515
+ body: [
516
+ {
517
+ type: 'ExpressionStatement',
518
+ expression: {
519
+ type: 'CallExpression',
520
+ loc: { start: { line: 8 }, end: { line: 11 } },
521
+ callee: {
522
+ type: 'MemberExpression',
523
+ object: { type: 'Identifier', name: 'ConfigModule' },
524
+ property: { type: 'Identifier', name: 'forRoot' },
525
+ },
526
+ arguments: [
527
+ {
528
+ type: 'ObjectExpression',
529
+ properties: [
530
+ {
531
+ type: 'ObjectProperty',
532
+ key: { type: 'Identifier', name: 'isGlobal' },
533
+ value: { type: 'BooleanLiteral', value: true },
534
+ },
535
+ ],
536
+ },
537
+ ],
538
+ },
539
+ },
540
+ ],
541
+ };
542
+ const validatedAst = {
543
+ type: 'CallExpression',
544
+ loc: { start: { line: 18 }, end: { line: 21 } },
545
+ callee: {
546
+ type: 'MemberExpression',
547
+ object: { type: 'Identifier', name: 'ConfigModule' },
548
+ property: { type: 'Identifier', name: 'forRoot' },
549
+ },
550
+ arguments: [
551
+ {
552
+ type: 'ObjectExpression',
553
+ properties: [
554
+ {
555
+ type: 'ObjectProperty',
556
+ key: { type: 'Identifier', name: 'validationSchema' },
557
+ value: { type: 'Identifier', name: 'envSchema' },
558
+ },
559
+ ],
560
+ },
561
+ ],
562
+ };
563
+
564
+ assert.equal(hasBackendConfigModuleWithoutValidation(missingValidationAst), true);
565
+ assert.deepEqual(findBackendConfigModuleWithoutValidationLines(missingValidationAst), [8]);
566
+ assert.equal(hasBackendConfigModuleWithoutValidation(validatedAst), false);
567
+ });
568
+
320
569
  test('hasBackendMagicNumberLiteral detecta numeros inline y permite constantes nombradas', () => {
321
570
  const inlineMagicNumberAst = {
322
571
  type: 'ReturnStatement',
@@ -1094,6 +1343,55 @@ test('hasBackendStringLiteralUnionEnumCandidate detecta uniones de literales str
1094
1343
  assert.equal(hasBackendStringLiteralUnionEnumCandidate(openUnionAst), false);
1095
1344
  });
1096
1345
 
1346
+ test('hasBackendSensitiveCacheWrite detecta datos sensibles escritos en cache', () => {
1347
+ const sensitiveCacheAst = {
1348
+ type: 'Program',
1349
+ body: [
1350
+ {
1351
+ type: 'ExpressionStatement',
1352
+ expression: {
1353
+ type: 'CallExpression',
1354
+ loc: { start: { line: 18 }, end: { line: 18 } },
1355
+ callee: {
1356
+ type: 'MemberExpression',
1357
+ object: { type: 'Identifier', name: 'cacheManager' },
1358
+ property: { type: 'Identifier', name: 'set' },
1359
+ },
1360
+ arguments: [
1361
+ { type: 'StringLiteral', value: 'session:token' },
1362
+ { type: 'Identifier', name: 'accessToken' },
1363
+ ],
1364
+ },
1365
+ },
1366
+ ],
1367
+ };
1368
+ const safeCacheAst = {
1369
+ type: 'Program',
1370
+ body: [
1371
+ {
1372
+ type: 'ExpressionStatement',
1373
+ expression: {
1374
+ type: 'CallExpression',
1375
+ loc: { start: { line: 27 }, end: { line: 27 } },
1376
+ callee: {
1377
+ type: 'MemberExpression',
1378
+ object: { type: 'Identifier', name: 'cacheManager' },
1379
+ property: { type: 'Identifier', name: 'set' },
1380
+ },
1381
+ arguments: [
1382
+ { type: 'StringLiteral', value: 'products:list' },
1383
+ { type: 'Identifier', name: 'products' },
1384
+ ],
1385
+ },
1386
+ },
1387
+ ],
1388
+ };
1389
+
1390
+ assert.equal(hasBackendSensitiveCacheWrite(sensitiveCacheAst), true);
1391
+ assert.deepEqual(findBackendSensitiveCacheWriteLines(sensitiveCacheAst), [18]);
1392
+ assert.equal(hasBackendSensitiveCacheWrite(safeCacheAst), false);
1393
+ });
1394
+
1097
1395
  test('hasBackendHardDeleteWithoutSoftDelete detecta borrados fisicos en persistencia backend', () => {
1098
1396
  const hardDeleteAst = {
1099
1397
  type: 'Program',
@@ -2880,6 +3178,71 @@ test('hasDtoPropertyWithoutValidation detecta DTOs sin validacion class-validato
2880
3178
  assert.deepEqual(match.related_nodes, ['email']);
2881
3179
  });
2882
3180
 
3181
+ test('hasDtoPropertyWithoutApiProperty detecta DTOs sin decoradores Swagger', () => {
3182
+ const missingSwaggerAst = {
3183
+ type: 'Program',
3184
+ body: [
3185
+ {
3186
+ type: 'ClassDeclaration',
3187
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
3188
+ body: {
3189
+ type: 'ClassBody',
3190
+ body: [
3191
+ {
3192
+ type: 'ClassProperty',
3193
+ key: { type: 'Identifier', name: 'email' },
3194
+ loc: { start: { line: 7 }, end: { line: 7 } },
3195
+ decorators: [
3196
+ {
3197
+ type: 'Decorator',
3198
+ expression: {
3199
+ type: 'CallExpression',
3200
+ callee: { type: 'Identifier', name: 'IsEmail' },
3201
+ arguments: [],
3202
+ },
3203
+ },
3204
+ ],
3205
+ },
3206
+ ],
3207
+ },
3208
+ },
3209
+ ],
3210
+ };
3211
+ const swaggerDtoAst = {
3212
+ type: 'Program',
3213
+ body: [
3214
+ {
3215
+ type: 'ClassDeclaration',
3216
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
3217
+ body: {
3218
+ type: 'ClassBody',
3219
+ body: [
3220
+ {
3221
+ type: 'ClassProperty',
3222
+ key: { type: 'Identifier', name: 'email' },
3223
+ loc: { start: { line: 14 }, end: { line: 14 } },
3224
+ decorators: [
3225
+ {
3226
+ type: 'Decorator',
3227
+ expression: {
3228
+ type: 'CallExpression',
3229
+ callee: { type: 'Identifier', name: 'ApiProperty' },
3230
+ arguments: [],
3231
+ },
3232
+ },
3233
+ ],
3234
+ },
3235
+ ],
3236
+ },
3237
+ },
3238
+ ],
3239
+ };
3240
+
3241
+ assert.equal(hasDtoPropertyWithoutApiProperty(missingSwaggerAst), true);
3242
+ assert.deepEqual(findDtoPropertyWithoutApiPropertyLines(missingSwaggerAst), [7]);
3243
+ assert.equal(hasDtoPropertyWithoutApiProperty(swaggerDtoAst), false);
3244
+ });
3245
+
2883
3246
  test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin ValidateNested y Type', () => {
2884
3247
  const missingNestedValidationAst = {
2885
3248
  type: 'Program',
@@ -3119,6 +3482,67 @@ test('hasBackendMissingHelmetSecurityHeaders detecta bootstrap NestJS sin Helmet
3119
3482
  assert.equal(hasBackendMissingHelmetSecurityHeaders(helmetAst), false);
3120
3483
  });
3121
3484
 
3485
+ test('hasBackendMissingCompressionMiddleware detecta bootstrap NestJS sin compression', () => {
3486
+ const missingCompressionAst = {
3487
+ type: 'Program',
3488
+ body: [
3489
+ {
3490
+ type: 'ExpressionStatement',
3491
+ expression: {
3492
+ type: 'CallExpression',
3493
+ loc: { start: { line: 9 }, end: { line: 9 } },
3494
+ callee: {
3495
+ type: 'MemberExpression',
3496
+ object: { type: 'Identifier', name: 'NestFactory' },
3497
+ property: { type: 'Identifier', name: 'create' },
3498
+ },
3499
+ arguments: [],
3500
+ },
3501
+ },
3502
+ ],
3503
+ };
3504
+ const compressionAst = {
3505
+ type: 'Program',
3506
+ body: [
3507
+ {
3508
+ type: 'ExpressionStatement',
3509
+ expression: {
3510
+ type: 'CallExpression',
3511
+ loc: { start: { line: 7 }, end: { line: 7 } },
3512
+ callee: {
3513
+ type: 'MemberExpression',
3514
+ object: { type: 'Identifier', name: 'NestFactory' },
3515
+ property: { type: 'Identifier', name: 'create' },
3516
+ },
3517
+ arguments: [],
3518
+ },
3519
+ },
3520
+ {
3521
+ type: 'ExpressionStatement',
3522
+ expression: {
3523
+ type: 'CallExpression',
3524
+ callee: {
3525
+ type: 'MemberExpression',
3526
+ object: { type: 'Identifier', name: 'app' },
3527
+ property: { type: 'Identifier', name: 'use' },
3528
+ },
3529
+ arguments: [
3530
+ {
3531
+ type: 'CallExpression',
3532
+ callee: { type: 'Identifier', name: 'compression' },
3533
+ arguments: [],
3534
+ },
3535
+ ],
3536
+ },
3537
+ },
3538
+ ],
3539
+ };
3540
+
3541
+ assert.equal(hasBackendMissingCompressionMiddleware(missingCompressionAst), true);
3542
+ assert.deepEqual(findBackendMissingCompressionMiddlewareLines(missingCompressionAst), [9]);
3543
+ assert.equal(hasBackendMissingCompressionMiddleware(compressionAst), false);
3544
+ });
3545
+
3122
3546
  test('hasBackendControllerRouteWithoutGuard detecta rutas NestJS sin UseGuards', () => {
3123
3547
  const unguardedRouteAst = {
3124
3548
  type: 'Program',
@@ -3190,6 +3614,85 @@ test('hasBackendControllerRouteWithoutGuard detecta rutas NestJS sin UseGuards',
3190
3614
  assert.deepEqual(findBackendControllerRouteWithoutGuardLines(unguardedRouteAst), [12]);
3191
3615
  });
3192
3616
 
3617
+ test('hasBackendAuthRouteWithoutThrottle detecta endpoints auth sin rate limiting', () => {
3618
+ const loginWithoutThrottleAst = {
3619
+ type: 'Program',
3620
+ body: [
3621
+ {
3622
+ type: 'ClassDeclaration',
3623
+ decorators: [
3624
+ {
3625
+ type: 'Decorator',
3626
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
3627
+ },
3628
+ ],
3629
+ body: {
3630
+ type: 'ClassBody',
3631
+ body: [
3632
+ {
3633
+ type: 'ClassMethod',
3634
+ key: { type: 'Identifier', name: 'login' },
3635
+ loc: { start: { line: 21 }, end: { line: 24 } },
3636
+ decorators: [
3637
+ {
3638
+ type: 'Decorator',
3639
+ expression: {
3640
+ type: 'CallExpression',
3641
+ callee: { type: 'Identifier', name: 'Post' },
3642
+ arguments: [{ type: 'StringLiteral', value: 'login' }],
3643
+ },
3644
+ },
3645
+ ],
3646
+ },
3647
+ ],
3648
+ },
3649
+ },
3650
+ ],
3651
+ };
3652
+ const classThrottledLoginAst = {
3653
+ type: 'Program',
3654
+ body: [
3655
+ {
3656
+ type: 'ClassDeclaration',
3657
+ decorators: [
3658
+ {
3659
+ type: 'Decorator',
3660
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
3661
+ },
3662
+ {
3663
+ type: 'Decorator',
3664
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Throttle' }, arguments: [] },
3665
+ },
3666
+ ],
3667
+ body: {
3668
+ type: 'ClassBody',
3669
+ body: [
3670
+ {
3671
+ type: 'ClassMethod',
3672
+ key: { type: 'Identifier', name: 'login' },
3673
+ loc: { start: { line: 34 }, end: { line: 37 } },
3674
+ decorators: [
3675
+ {
3676
+ type: 'Decorator',
3677
+ expression: {
3678
+ type: 'CallExpression',
3679
+ callee: { type: 'Identifier', name: 'Post' },
3680
+ arguments: [{ type: 'StringLiteral', value: 'login' }],
3681
+ },
3682
+ },
3683
+ ],
3684
+ },
3685
+ ],
3686
+ },
3687
+ },
3688
+ ],
3689
+ };
3690
+
3691
+ assert.equal(hasBackendAuthRouteWithoutThrottle(loginWithoutThrottleAst), true);
3692
+ assert.deepEqual(findBackendAuthRouteWithoutThrottleLines(loginWithoutThrottleAst), [21]);
3693
+ assert.equal(hasBackendAuthRouteWithoutThrottle(classThrottledLoginAst), false);
3694
+ });
3695
+
3193
3696
  test('hasBackendControllerRouteWithoutRoles detecta rutas NestJS protegidas sin RBAC explicito', () => {
3194
3697
  const guardedWithoutRolesAst = {
3195
3698
  type: 'Program',
@@ -3280,3 +3783,69 @@ test('hasBackendControllerRouteWithoutRoles detecta rutas NestJS protegidas sin
3280
3783
  assert.equal(hasBackendControllerRouteWithoutRoles(methodRolesAst), false);
3281
3784
  assert.equal(hasBackendControllerRouteWithoutRoles(unguardedRouteAst), false);
3282
3785
  });
3786
+
3787
+ test('hasBackendEventHandlerWithoutOnEvent detecta handlers backend sin decorador OnEvent', () => {
3788
+ const missingOnEventAst = {
3789
+ type: 'Program',
3790
+ body: [
3791
+ {
3792
+ type: 'ClassDeclaration',
3793
+ id: { type: 'Identifier', name: 'OrderCreatedEventHandler' },
3794
+ loc: { start: { line: 9 }, end: { line: 20 } },
3795
+ decorators: [
3796
+ {
3797
+ type: 'Decorator',
3798
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Injectable' }, arguments: [] },
3799
+ },
3800
+ ],
3801
+ body: {
3802
+ type: 'ClassBody',
3803
+ body: [
3804
+ {
3805
+ type: 'ClassMethod',
3806
+ key: { type: 'Identifier', name: 'handleOrderCreatedEvent' },
3807
+ loc: { start: { line: 14 }, end: { line: 18 } },
3808
+ decorators: [],
3809
+ },
3810
+ ],
3811
+ },
3812
+ },
3813
+ ],
3814
+ };
3815
+ const decoratedMethodAst = {
3816
+ type: 'Program',
3817
+ body: [
3818
+ {
3819
+ type: 'ClassDeclaration',
3820
+ id: { type: 'Identifier', name: 'OrderCreatedEventHandler' },
3821
+ loc: { start: { line: 29 }, end: { line: 40 } },
3822
+ decorators: [
3823
+ {
3824
+ type: 'Decorator',
3825
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Injectable' }, arguments: [] },
3826
+ },
3827
+ ],
3828
+ body: {
3829
+ type: 'ClassBody',
3830
+ body: [
3831
+ {
3832
+ type: 'ClassMethod',
3833
+ key: { type: 'Identifier', name: 'handleOrderCreatedEvent' },
3834
+ loc: { start: { line: 34 }, end: { line: 38 } },
3835
+ decorators: [
3836
+ {
3837
+ type: 'Decorator',
3838
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'OnEvent' }, arguments: [] },
3839
+ },
3840
+ ],
3841
+ },
3842
+ ],
3843
+ },
3844
+ },
3845
+ ],
3846
+ };
3847
+
3848
+ assert.equal(hasBackendEventHandlerWithoutOnEvent(missingOnEventAst), true);
3849
+ assert.deepEqual(findBackendEventHandlerWithoutOnEventLines(missingOnEventAst), [9, 14]);
3850
+ assert.equal(hasBackendEventHandlerWithoutOnEvent(decoratedMethodAst), false);
3851
+ });