pumuki 6.3.284 → 6.3.286

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.
@@ -6,6 +6,14 @@ import {
6
6
  findDtoPropertyWithoutValidationMatch,
7
7
  findDtoNestedPropertyWithoutNestedValidationMatch,
8
8
  findBackendControllerRouteWithoutGuardLines,
9
+ findBackendPermissiveCorsConfigurationLines,
10
+ findBackendStringLiteralUnionEnumCandidateLines,
11
+ findBackendHardDeleteWithoutSoftDeleteLines,
12
+ findBackendLogWithoutContextLines,
13
+ findBackendInconsistentErrorResponseLines,
14
+ findBackendErrorPayloadWithSuccessStatusLines,
15
+ findBackendControllerEntityResponseLines,
16
+ findBackendRawThrowExpressionLines,
9
17
  findFrameworkDependencyImportMatch,
10
18
  findMixedCommandQueryClassMatch,
11
19
  findMixedCommandQueryInterfaceMatch,
@@ -29,6 +37,13 @@ import {
29
37
  hasDtoPropertyWithoutValidation,
30
38
  hasDtoNestedPropertyWithoutNestedValidation,
31
39
  hasBackendControllerRouteWithoutGuard,
40
+ hasBackendPermissiveCorsConfiguration,
41
+ hasBackendStringLiteralUnionEnumCandidate,
42
+ hasBackendHardDeleteWithoutSoftDelete,
43
+ hasBackendInconsistentErrorResponse,
44
+ hasBackendErrorPayloadWithSuccessStatus,
45
+ hasBackendControllerEntityResponse,
46
+ hasBackendRawThrowExpression,
32
47
  hasEmptyCatchClause,
33
48
  hasEvalCall,
34
49
  hasExplicitAnyType,
@@ -38,6 +53,7 @@ import {
38
53
  hasNestedCallbackUsage,
39
54
  hasNestedIfElseStatement,
40
55
  hasNestJsConstructorDependencyWithoutDecorator,
56
+ hasBackendLogWithoutContext,
41
57
  hasBackendMagicNumberLiteral,
42
58
  hasBackendGenericErrorThrow,
43
59
  hasBackendProductionMockOrSpy,
@@ -215,6 +231,82 @@ test('hasSensitiveDataLoggingCall detecta datos sensibles en sinks de logging',
215
231
  assert.equal(hasSensitiveDataLoggingCall(safeLoggerAst), false);
216
232
  });
217
233
 
234
+ test('hasBackendLogWithoutContext detecta logs backend sin request user o trace context', () => {
235
+ const errorWithoutContextAst = {
236
+ type: 'File',
237
+ program: {
238
+ type: 'Program',
239
+ body: [
240
+ {
241
+ type: 'ExpressionStatement',
242
+ loc: { start: { line: 8 } },
243
+ expression: {
244
+ type: 'CallExpression',
245
+ loc: { start: { line: 8 } },
246
+ callee: {
247
+ type: 'MemberExpression',
248
+ computed: false,
249
+ object: { type: 'Identifier', name: 'logger' },
250
+ property: { type: 'Identifier', name: 'error' },
251
+ },
252
+ arguments: [{ type: 'Identifier', name: 'error' }],
253
+ },
254
+ },
255
+ {
256
+ type: 'ExpressionStatement',
257
+ loc: { start: { line: 12 } },
258
+ expression: {
259
+ type: 'CallExpression',
260
+ loc: { start: { line: 12 } },
261
+ callee: {
262
+ type: 'MemberExpression',
263
+ computed: false,
264
+ object: { type: 'Identifier', name: 'logger' },
265
+ property: { type: 'Identifier', name: 'error' },
266
+ },
267
+ arguments: [
268
+ {
269
+ type: 'ObjectExpression',
270
+ properties: [
271
+ {
272
+ type: 'ObjectProperty',
273
+ key: { type: 'Identifier', name: 'message' },
274
+ value: { type: 'MemberExpression', property: { type: 'Identifier', name: 'message' } },
275
+ },
276
+ ],
277
+ },
278
+ ],
279
+ },
280
+ },
281
+ ],
282
+ },
283
+ };
284
+ const contextualLoggerAst = {
285
+ type: 'CallExpression',
286
+ callee: {
287
+ type: 'MemberExpression',
288
+ computed: false,
289
+ object: { type: 'Identifier', name: 'logger' },
290
+ property: { type: 'Identifier', name: 'error' },
291
+ },
292
+ arguments: [
293
+ {
294
+ type: 'ObjectExpression',
295
+ properties: [
296
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'message' }, value: { type: 'Identifier', name: 'message' } },
297
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'requestId' }, value: { type: 'Identifier', name: 'requestId' } },
298
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'traceId' }, value: { type: 'Identifier', name: 'traceId' } },
299
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'userId' }, value: { type: 'Identifier', name: 'userId' } },
300
+ ],
301
+ },
302
+ ],
303
+ };
304
+
305
+ assert.equal(hasBackendLogWithoutContext(errorWithoutContextAst), true);
306
+ assert.deepEqual(findBackendLogWithoutContextLines(errorWithoutContextAst), [8, 12]);
307
+ assert.equal(hasBackendLogWithoutContext(contextualLoggerAst), false);
308
+ });
309
+
218
310
  test('hasBackendMagicNumberLiteral detecta numeros inline y permite constantes nombradas', () => {
219
311
  const inlineMagicNumberAst = {
220
312
  type: 'ReturnStatement',
@@ -265,6 +357,41 @@ test('hasBackendGenericErrorThrow detecta throw new Error y permite excepciones
265
357
  assert.equal(hasBackendGenericErrorThrow(specificErrorAst), false);
266
358
  });
267
359
 
360
+ test('hasBackendRawThrowExpression detecta throw de valores crudos y permite excepciones tipadas', () => {
361
+ const rawThrowAst = {
362
+ type: 'Program',
363
+ body: [
364
+ {
365
+ type: 'ThrowStatement',
366
+ loc: { start: { line: 6 }, end: { line: 6 } },
367
+ argument: { type: 'StringLiteral', value: 'invalid order' },
368
+ },
369
+ {
370
+ type: 'ThrowStatement',
371
+ loc: { start: { line: 9 }, end: { line: 9 } },
372
+ argument: { type: 'Identifier', name: 'error' },
373
+ },
374
+ ],
375
+ };
376
+ const typedThrowAst = {
377
+ type: 'Program',
378
+ body: [
379
+ {
380
+ type: 'ThrowStatement',
381
+ argument: {
382
+ type: 'NewExpression',
383
+ callee: { type: 'Identifier', name: 'ValidationException' },
384
+ arguments: [{ type: 'StringLiteral', value: 'invalid order' }],
385
+ },
386
+ },
387
+ ],
388
+ };
389
+
390
+ assert.equal(hasBackendRawThrowExpression(rawThrowAst), true);
391
+ assert.deepEqual(findBackendRawThrowExpressionLines(rawThrowAst), [6, 9]);
392
+ assert.equal(hasBackendRawThrowExpression(typedThrowAst), false);
393
+ });
394
+
268
395
  test('hasBackendProductionMockOrSpy detecta mocks y spies de test en codigo production', () => {
269
396
  const jestMockAst = {
270
397
  type: 'CallExpression',
@@ -344,6 +471,250 @@ test('hasBackendErrorStackInHttpResponse detecta stack trace expuesto en respues
344
471
  assert.equal(hasBackendErrorStackInHttpResponse(internalLoggingAst), false);
345
472
  });
346
473
 
474
+ test('hasBackendInconsistentErrorResponse detecta respuestas de error sin contrato completo', () => {
475
+ const inconsistentErrorResponseAst = {
476
+ type: 'Program',
477
+ body: [
478
+ {
479
+ type: 'TryStatement',
480
+ block: { type: 'BlockStatement', body: [] },
481
+ handler: {
482
+ type: 'CatchClause',
483
+ body: {
484
+ type: 'BlockStatement',
485
+ body: [
486
+ {
487
+ type: 'ExpressionStatement',
488
+ expression: {
489
+ type: 'CallExpression',
490
+ loc: { start: { line: 12 }, end: { line: 12 } },
491
+ callee: {
492
+ type: 'MemberExpression',
493
+ computed: false,
494
+ object: { type: 'Identifier', name: 'res' },
495
+ property: { type: 'Identifier', name: 'json' },
496
+ },
497
+ arguments: [
498
+ {
499
+ type: 'ObjectExpression',
500
+ properties: [
501
+ {
502
+ type: 'ObjectProperty',
503
+ key: { type: 'Identifier', name: 'message' },
504
+ value: { type: 'StringLiteral', value: 'failed' },
505
+ },
506
+ ],
507
+ },
508
+ ],
509
+ },
510
+ },
511
+ ],
512
+ },
513
+ },
514
+ },
515
+ {
516
+ type: 'ExpressionStatement',
517
+ expression: {
518
+ type: 'CallExpression',
519
+ loc: { start: { line: 20 }, end: { line: 20 } },
520
+ callee: {
521
+ type: 'MemberExpression',
522
+ computed: false,
523
+ object: {
524
+ type: 'CallExpression',
525
+ callee: {
526
+ type: 'MemberExpression',
527
+ computed: false,
528
+ object: { type: 'Identifier', name: 'res' },
529
+ property: { type: 'Identifier', name: 'status' },
530
+ },
531
+ arguments: [{ type: 'NumericLiteral', value: 404 }],
532
+ },
533
+ property: { type: 'Identifier', name: 'json' },
534
+ },
535
+ arguments: [
536
+ {
537
+ type: 'ObjectExpression',
538
+ properties: [
539
+ {
540
+ type: 'ObjectProperty',
541
+ key: { type: 'Identifier', name: 'error' },
542
+ value: { type: 'StringLiteral', value: 'not found' },
543
+ },
544
+ ],
545
+ },
546
+ ],
547
+ },
548
+ },
549
+ ],
550
+ };
551
+ const consistentErrorResponseAst = {
552
+ type: 'CallExpression',
553
+ callee: {
554
+ type: 'MemberExpression',
555
+ computed: false,
556
+ object: {
557
+ type: 'CallExpression',
558
+ callee: {
559
+ type: 'MemberExpression',
560
+ computed: false,
561
+ object: { type: 'Identifier', name: 'res' },
562
+ property: { type: 'Identifier', name: 'status' },
563
+ },
564
+ arguments: [{ type: 'NumericLiteral', value: 400 }],
565
+ },
566
+ property: { type: 'Identifier', name: 'json' },
567
+ },
568
+ arguments: [
569
+ {
570
+ type: 'ObjectExpression',
571
+ properties: [
572
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'statusCode' } },
573
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'message' } },
574
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'timestamp' } },
575
+ { type: 'ObjectProperty', key: { type: 'Identifier', name: 'path' } },
576
+ ],
577
+ },
578
+ ],
579
+ };
580
+
581
+ assert.equal(hasBackendInconsistentErrorResponse(inconsistentErrorResponseAst), true);
582
+ assert.deepEqual(findBackendInconsistentErrorResponseLines(inconsistentErrorResponseAst), [
583
+ 12,
584
+ 20,
585
+ ]);
586
+ assert.equal(hasBackendInconsistentErrorResponse(consistentErrorResponseAst), false);
587
+ });
588
+
589
+ test('hasBackendErrorPayloadWithSuccessStatus detecta payload de error con status 2xx o 3xx', () => {
590
+ const wrongStatusAst = {
591
+ type: 'Program',
592
+ body: [
593
+ {
594
+ type: 'ExpressionStatement',
595
+ expression: {
596
+ type: 'CallExpression',
597
+ loc: { start: { line: 9 }, end: { line: 9 } },
598
+ callee: {
599
+ type: 'MemberExpression',
600
+ computed: false,
601
+ object: {
602
+ type: 'CallExpression',
603
+ callee: {
604
+ type: 'MemberExpression',
605
+ computed: false,
606
+ object: { type: 'Identifier', name: 'res' },
607
+ property: { type: 'Identifier', name: 'status' },
608
+ },
609
+ arguments: [{ type: 'NumericLiteral', value: 200 }],
610
+ },
611
+ property: { type: 'Identifier', name: 'json' },
612
+ },
613
+ arguments: [
614
+ {
615
+ type: 'ObjectExpression',
616
+ properties: [
617
+ {
618
+ type: 'ObjectProperty',
619
+ key: { type: 'Identifier', name: 'error' },
620
+ value: { type: 'StringLiteral', value: 'invalid order' },
621
+ },
622
+ ],
623
+ },
624
+ ],
625
+ },
626
+ },
627
+ ],
628
+ };
629
+ const correctStatusAst = {
630
+ type: 'CallExpression',
631
+ callee: {
632
+ type: 'MemberExpression',
633
+ computed: false,
634
+ object: {
635
+ type: 'CallExpression',
636
+ callee: {
637
+ type: 'MemberExpression',
638
+ computed: false,
639
+ object: { type: 'Identifier', name: 'res' },
640
+ property: { type: 'Identifier', name: 'status' },
641
+ },
642
+ arguments: [{ type: 'NumericLiteral', value: 400 }],
643
+ },
644
+ property: { type: 'Identifier', name: 'json' },
645
+ },
646
+ arguments: [
647
+ {
648
+ type: 'ObjectExpression',
649
+ properties: [
650
+ {
651
+ type: 'ObjectProperty',
652
+ key: { type: 'Identifier', name: 'error' },
653
+ value: { type: 'StringLiteral', value: 'invalid order' },
654
+ },
655
+ ],
656
+ },
657
+ ],
658
+ };
659
+
660
+ assert.equal(hasBackendErrorPayloadWithSuccessStatus(wrongStatusAst), true);
661
+ assert.deepEqual(findBackendErrorPayloadWithSuccessStatusLines(wrongStatusAst), [9]);
662
+ assert.equal(hasBackendErrorPayloadWithSuccessStatus(correctStatusAst), false);
663
+ });
664
+
665
+ test('hasBackendControllerEntityResponse detecta entidades expuestas por controllers', () => {
666
+ const entityReturnTypeAst = {
667
+ type: 'Program',
668
+ body: [
669
+ {
670
+ type: 'ClassMethod',
671
+ loc: { start: { line: 7 }, end: { line: 10 } },
672
+ key: { type: 'Identifier', name: 'getOrder' },
673
+ returnType: {
674
+ type: 'TSTypeAnnotation',
675
+ typeAnnotation: {
676
+ type: 'TSTypeReference',
677
+ typeName: { type: 'Identifier', name: 'Promise' },
678
+ typeParameters: {
679
+ type: 'TSTypeParameterInstantiation',
680
+ params: [
681
+ {
682
+ type: 'TSTypeReference',
683
+ typeName: { type: 'Identifier', name: 'OrderEntity' },
684
+ },
685
+ ],
686
+ },
687
+ },
688
+ },
689
+ },
690
+ {
691
+ type: 'ReturnStatement',
692
+ loc: { start: { line: 14 }, end: { line: 14 } },
693
+ argument: {
694
+ type: 'NewExpression',
695
+ callee: { type: 'Identifier', name: 'UserEntity' },
696
+ arguments: [],
697
+ },
698
+ },
699
+ ],
700
+ };
701
+ const dtoReturnTypeAst = {
702
+ type: 'ClassMethod',
703
+ key: { type: 'Identifier', name: 'getOrder' },
704
+ returnType: {
705
+ type: 'TSTypeAnnotation',
706
+ typeAnnotation: {
707
+ type: 'TSTypeReference',
708
+ typeName: { type: 'Identifier', name: 'OrderResponseDto' },
709
+ },
710
+ },
711
+ };
712
+
713
+ assert.equal(hasBackendControllerEntityResponse(entityReturnTypeAst), true);
714
+ assert.deepEqual(findBackendControllerEntityResponseLines(entityReturnTypeAst), [7, 14]);
715
+ assert.equal(hasBackendControllerEntityResponse(dtoReturnTypeAst), false);
716
+ });
717
+
347
718
  test('hasBackendAnemicDomainModel detecta entidades de dominio anemicas', () => {
348
719
  const anemicEntityAst = {
349
720
  type: 'ClassDeclaration',
@@ -390,6 +761,190 @@ test('hasBackendAnemicDomainModel detecta entidades de dominio anemicas', () =>
390
761
  assert.equal(hasBackendAnemicDomainModel(richEntityAst), false);
391
762
  });
392
763
 
764
+ test('hasBackendPermissiveCorsConfiguration detecta CORS permisivo y permite origenes acotados', () => {
765
+ const permissiveAst = {
766
+ type: 'Program',
767
+ body: [
768
+ {
769
+ type: 'CallExpression',
770
+ loc: { start: { line: 3 }, end: { line: 3 } },
771
+ callee: {
772
+ type: 'MemberExpression',
773
+ computed: false,
774
+ object: { type: 'Identifier', name: 'app' },
775
+ property: { type: 'Identifier', name: 'enableCors' },
776
+ },
777
+ arguments: [],
778
+ },
779
+ {
780
+ type: 'CallExpression',
781
+ loc: { start: { line: 7 }, end: { line: 7 } },
782
+ callee: {
783
+ type: 'MemberExpression',
784
+ computed: false,
785
+ object: { type: 'Identifier', name: 'NestFactory' },
786
+ property: { type: 'Identifier', name: 'create' },
787
+ },
788
+ arguments: [
789
+ { type: 'Identifier', name: 'AppModule' },
790
+ {
791
+ type: 'ObjectExpression',
792
+ properties: [
793
+ {
794
+ type: 'ObjectProperty',
795
+ key: { type: 'Identifier', name: 'cors' },
796
+ value: { type: 'BooleanLiteral', value: true },
797
+ },
798
+ ],
799
+ },
800
+ ],
801
+ },
802
+ {
803
+ type: 'CallExpression',
804
+ loc: { start: { line: 11 }, end: { line: 11 } },
805
+ callee: { type: 'Identifier', name: 'cors' },
806
+ arguments: [
807
+ {
808
+ type: 'ObjectExpression',
809
+ properties: [
810
+ {
811
+ type: 'ObjectProperty',
812
+ key: { type: 'Identifier', name: 'origin' },
813
+ value: { type: 'StringLiteral', value: '*' },
814
+ },
815
+ ],
816
+ },
817
+ ],
818
+ },
819
+ ],
820
+ };
821
+ const safeAst = {
822
+ type: 'Program',
823
+ body: [
824
+ {
825
+ type: 'CallExpression',
826
+ callee: {
827
+ type: 'MemberExpression',
828
+ computed: false,
829
+ object: { type: 'Identifier', name: 'app' },
830
+ property: { type: 'Identifier', name: 'enableCors' },
831
+ },
832
+ arguments: [
833
+ {
834
+ type: 'ObjectExpression',
835
+ properties: [
836
+ {
837
+ type: 'ObjectProperty',
838
+ key: { type: 'Identifier', name: 'origin' },
839
+ value: { type: 'Identifier', name: 'allowedOrigins' },
840
+ },
841
+ ],
842
+ },
843
+ ],
844
+ },
845
+ ],
846
+ };
847
+
848
+ assert.equal(hasBackendPermissiveCorsConfiguration(permissiveAst), true);
849
+ assert.deepEqual(findBackendPermissiveCorsConfigurationLines(permissiveAst), [
850
+ 3,
851
+ 7,
852
+ 11,
853
+ ]);
854
+ assert.equal(hasBackendPermissiveCorsConfiguration(safeAst), false);
855
+ });
856
+
857
+ test('hasBackendStringLiteralUnionEnumCandidate detecta uniones de literales string para enum', () => {
858
+ const enumCandidateAst = {
859
+ type: 'Program',
860
+ body: [
861
+ {
862
+ type: 'TSTypeAliasDeclaration',
863
+ id: { type: 'Identifier', name: 'OrderStatus' },
864
+ typeAnnotation: {
865
+ type: 'TSUnionType',
866
+ loc: { start: { line: 4 }, end: { line: 4 } },
867
+ types: [
868
+ { type: 'TSLiteralType', literal: { type: 'StringLiteral', value: 'pending' } },
869
+ { type: 'TSLiteralType', literal: { type: 'StringLiteral', value: 'paid' } },
870
+ { type: 'TSLiteralType', literal: { type: 'StringLiteral', value: 'cancelled' } },
871
+ ],
872
+ },
873
+ },
874
+ ],
875
+ };
876
+ const openUnionAst = {
877
+ type: 'Program',
878
+ body: [
879
+ {
880
+ type: 'TSUnionType',
881
+ types: [
882
+ { type: 'TSLiteralType', literal: { type: 'StringLiteral', value: 'pending' } },
883
+ { type: 'TSStringKeyword' },
884
+ ],
885
+ },
886
+ ],
887
+ };
888
+
889
+ assert.equal(hasBackendStringLiteralUnionEnumCandidate(enumCandidateAst), true);
890
+ assert.deepEqual(findBackendStringLiteralUnionEnumCandidateLines(enumCandidateAst), [4]);
891
+ assert.equal(hasBackendStringLiteralUnionEnumCandidate(openUnionAst), false);
892
+ });
893
+
894
+ test('hasBackendHardDeleteWithoutSoftDelete detecta borrados fisicos en persistencia backend', () => {
895
+ const hardDeleteAst = {
896
+ type: 'Program',
897
+ body: [
898
+ {
899
+ type: 'CallExpression',
900
+ loc: { start: { line: 8 }, end: { line: 8 } },
901
+ callee: {
902
+ type: 'MemberExpression',
903
+ computed: false,
904
+ object: { type: 'Identifier', name: 'ordersRepository' },
905
+ property: { type: 'Identifier', name: 'delete' },
906
+ },
907
+ arguments: [{ type: 'Identifier', name: 'orderId' }],
908
+ },
909
+ {
910
+ type: 'CallExpression',
911
+ loc: { start: { line: 12 }, end: { line: 12 } },
912
+ callee: {
913
+ type: 'MemberExpression',
914
+ computed: false,
915
+ object: {
916
+ type: 'MemberExpression',
917
+ computed: false,
918
+ object: { type: 'Identifier', name: 'prisma' },
919
+ property: { type: 'Identifier', name: 'order' },
920
+ },
921
+ property: { type: 'Identifier', name: 'deleteMany' },
922
+ },
923
+ arguments: [],
924
+ },
925
+ ],
926
+ };
927
+ const softDeleteAst = {
928
+ type: 'Program',
929
+ body: [
930
+ {
931
+ type: 'CallExpression',
932
+ callee: {
933
+ type: 'MemberExpression',
934
+ computed: false,
935
+ object: { type: 'Identifier', name: 'ordersRepository' },
936
+ property: { type: 'Identifier', name: 'softDelete' },
937
+ },
938
+ arguments: [{ type: 'Identifier', name: 'orderId' }],
939
+ },
940
+ ],
941
+ };
942
+
943
+ assert.equal(hasBackendHardDeleteWithoutSoftDelete(hardDeleteAst), true);
944
+ assert.deepEqual(findBackendHardDeleteWithoutSoftDeleteLines(hardDeleteAst), [8, 12]);
945
+ assert.equal(hasBackendHardDeleteWithoutSoftDelete(softDeleteAst), false);
946
+ });
947
+
393
948
  test('hasEvalCall detecta eval directo y descarta member eval', () => {
394
949
  const directEvalAst = {
395
950
  type: 'CallExpression',