pumuki 6.3.276 → 6.3.278

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.
@@ -3,12 +3,17 @@ import test from 'node:test';
3
3
  import {
4
4
  findEmptyCatchClauseLines,
5
5
  findConcreteDependencyInstantiationMatch,
6
+ findDtoPropertyWithoutValidationMatch,
7
+ findDtoNestedPropertyWithoutNestedValidationMatch,
8
+ findBackendControllerRouteWithoutGuardLines,
6
9
  findFrameworkDependencyImportMatch,
7
10
  findMixedCommandQueryClassMatch,
8
11
  findMixedCommandQueryInterfaceMatch,
9
12
  findOverrideMethodThrowingNotImplementedMatch,
10
13
  findTypeDiscriminatorSwitchMatch,
11
14
  findNetworkCallWithoutErrorHandlingLines,
15
+ findNestJsConstructorDependencyWithoutDecoratorMatch,
16
+ findPersistenceMutationWithoutAuditEventMatch,
12
17
  findRecordStringUnknownTypeLines,
13
18
  findUndefinedInBaseTypeUnionLines,
14
19
  findUnknownWithoutGuardLines,
@@ -21,6 +26,9 @@ import {
21
26
  hasDeleteOperator,
22
27
  hasDefaultExportedApiRouteHandler,
23
28
  hasDirectNetworkCall,
29
+ hasDtoPropertyWithoutValidation,
30
+ hasDtoNestedPropertyWithoutNestedValidation,
31
+ hasBackendControllerRouteWithoutGuard,
24
32
  hasEmptyCatchClause,
25
33
  hasEvalCall,
26
34
  hasExplicitAnyType,
@@ -29,8 +37,16 @@ import {
29
37
  hasInterpolatedUnsafeSqlCall,
30
38
  hasNestedCallbackUsage,
31
39
  hasNestedIfElseStatement,
40
+ hasNestJsConstructorDependencyWithoutDecorator,
41
+ hasBackendMagicNumberLiteral,
42
+ hasBackendGenericErrorThrow,
43
+ hasBackendProductionMockOrSpy,
44
+ hasBackendErrorStackInHttpResponse,
45
+ hasBackendAnemicDomainModel,
32
46
  hasNonSemanticClickableJsx,
47
+ hasSensitiveDataLoggingCall,
33
48
  hasSensitiveTokenInUrl,
49
+ hasPersistenceMutationWithoutAuditEvent,
34
50
  hasWeakBcryptSaltRounds,
35
51
  hasNetworkCallWithoutErrorHandling,
36
52
  hasMixedCommandQueryClass,
@@ -151,6 +167,229 @@ test('hasConsoleErrorCall detecta console.error y descarta metodos distintos', (
151
167
  assert.equal(hasConsoleErrorCall(warnAst), false);
152
168
  });
153
169
 
170
+ test('hasSensitiveDataLoggingCall detecta datos sensibles en sinks de logging', () => {
171
+ const sensitiveConsoleAst = {
172
+ type: 'CallExpression',
173
+ callee: {
174
+ type: 'MemberExpression',
175
+ computed: false,
176
+ object: { type: 'Identifier', name: 'console' },
177
+ property: { type: 'Identifier', name: 'log' },
178
+ },
179
+ arguments: [{ type: 'Identifier', name: 'accessToken' }],
180
+ };
181
+ const sensitiveLoggerAst = {
182
+ type: 'CallExpression',
183
+ callee: {
184
+ type: 'MemberExpression',
185
+ computed: false,
186
+ object: { type: 'Identifier', name: 'logger' },
187
+ property: { type: 'Identifier', name: 'error' },
188
+ },
189
+ arguments: [
190
+ {
191
+ type: 'ObjectExpression',
192
+ properties: [
193
+ {
194
+ type: 'ObjectProperty',
195
+ key: { type: 'Identifier', name: 'password' },
196
+ value: { type: 'Identifier', name: 'password' },
197
+ },
198
+ ],
199
+ },
200
+ ],
201
+ };
202
+ const safeLoggerAst = {
203
+ type: 'CallExpression',
204
+ callee: {
205
+ type: 'MemberExpression',
206
+ computed: false,
207
+ object: { type: 'Identifier', name: 'logger' },
208
+ property: { type: 'Identifier', name: 'info' },
209
+ },
210
+ arguments: [{ type: 'Identifier', name: 'orderId' }],
211
+ };
212
+
213
+ assert.equal(hasSensitiveDataLoggingCall(sensitiveConsoleAst), true);
214
+ assert.equal(hasSensitiveDataLoggingCall(sensitiveLoggerAst), true);
215
+ assert.equal(hasSensitiveDataLoggingCall(safeLoggerAst), false);
216
+ });
217
+
218
+ test('hasBackendMagicNumberLiteral detecta numeros inline y permite constantes nombradas', () => {
219
+ const inlineMagicNumberAst = {
220
+ type: 'ReturnStatement',
221
+ argument: { type: 'NumericLiteral', value: 42 },
222
+ };
223
+ const namedConstantAst = {
224
+ type: 'VariableDeclaration',
225
+ kind: 'const',
226
+ declarations: [
227
+ {
228
+ type: 'VariableDeclarator',
229
+ id: { type: 'Identifier', name: 'MAX_RETRY_ATTEMPTS' },
230
+ init: { type: 'NumericLiteral', value: 3 },
231
+ },
232
+ ],
233
+ };
234
+ const trivialCounterAst = {
235
+ type: 'BinaryExpression',
236
+ operator: '+',
237
+ left: { type: 'Identifier', name: 'count' },
238
+ right: { type: 'NumericLiteral', value: 1 },
239
+ };
240
+
241
+ assert.equal(hasBackendMagicNumberLiteral(inlineMagicNumberAst), true);
242
+ assert.equal(hasBackendMagicNumberLiteral(namedConstantAst), false);
243
+ assert.equal(hasBackendMagicNumberLiteral(trivialCounterAst), false);
244
+ });
245
+
246
+ test('hasBackendGenericErrorThrow detecta throw new Error y permite excepciones especificas', () => {
247
+ const genericErrorAst = {
248
+ type: 'ThrowStatement',
249
+ argument: {
250
+ type: 'NewExpression',
251
+ callee: { type: 'Identifier', name: 'Error' },
252
+ arguments: [{ type: 'StringLiteral', value: 'invalid order' }],
253
+ },
254
+ };
255
+ const specificErrorAst = {
256
+ type: 'ThrowStatement',
257
+ argument: {
258
+ type: 'NewExpression',
259
+ callee: { type: 'Identifier', name: 'ValidationException' },
260
+ arguments: [{ type: 'StringLiteral', value: 'invalid order' }],
261
+ },
262
+ };
263
+
264
+ assert.equal(hasBackendGenericErrorThrow(genericErrorAst), true);
265
+ assert.equal(hasBackendGenericErrorThrow(specificErrorAst), false);
266
+ });
267
+
268
+ test('hasBackendProductionMockOrSpy detecta mocks y spies de test en codigo production', () => {
269
+ const jestMockAst = {
270
+ type: 'CallExpression',
271
+ callee: {
272
+ type: 'MemberExpression',
273
+ computed: false,
274
+ object: { type: 'Identifier', name: 'jest' },
275
+ property: { type: 'Identifier', name: 'fn' },
276
+ },
277
+ arguments: [],
278
+ };
279
+ const chainedMockAst = {
280
+ type: 'CallExpression',
281
+ callee: {
282
+ type: 'MemberExpression',
283
+ computed: false,
284
+ object: { type: 'CallExpression', callee: { type: 'Identifier', name: 'loadUser' } },
285
+ property: { type: 'Identifier', name: 'mockResolvedValue' },
286
+ },
287
+ arguments: [],
288
+ };
289
+ const realFactoryAst = {
290
+ type: 'CallExpression',
291
+ callee: { type: 'Identifier', name: 'createUserRepository' },
292
+ arguments: [],
293
+ };
294
+
295
+ assert.equal(hasBackendProductionMockOrSpy(jestMockAst), true);
296
+ assert.equal(hasBackendProductionMockOrSpy(chainedMockAst), true);
297
+ assert.equal(hasBackendProductionMockOrSpy(realFactoryAst), false);
298
+ });
299
+
300
+ test('hasBackendErrorStackInHttpResponse detecta stack trace expuesto en respuesta HTTP', () => {
301
+ const exposedStackAst = {
302
+ type: 'CallExpression',
303
+ callee: {
304
+ type: 'MemberExpression',
305
+ computed: false,
306
+ object: { type: 'Identifier', name: 'res' },
307
+ property: { type: 'Identifier', name: 'json' },
308
+ },
309
+ arguments: [
310
+ {
311
+ type: 'ObjectExpression',
312
+ properties: [
313
+ {
314
+ type: 'ObjectProperty',
315
+ key: { type: 'Identifier', name: 'stack' },
316
+ value: {
317
+ type: 'MemberExpression',
318
+ object: { type: 'Identifier', name: 'error' },
319
+ property: { type: 'Identifier', name: 'stack' },
320
+ },
321
+ },
322
+ ],
323
+ },
324
+ ],
325
+ };
326
+ const internalLoggingAst = {
327
+ type: 'CallExpression',
328
+ callee: {
329
+ type: 'MemberExpression',
330
+ computed: false,
331
+ object: { type: 'Identifier', name: 'logger' },
332
+ property: { type: 'Identifier', name: 'error' },
333
+ },
334
+ arguments: [
335
+ {
336
+ type: 'MemberExpression',
337
+ object: { type: 'Identifier', name: 'error' },
338
+ property: { type: 'Identifier', name: 'stack' },
339
+ },
340
+ ],
341
+ };
342
+
343
+ assert.equal(hasBackendErrorStackInHttpResponse(exposedStackAst), true);
344
+ assert.equal(hasBackendErrorStackInHttpResponse(internalLoggingAst), false);
345
+ });
346
+
347
+ test('hasBackendAnemicDomainModel detecta entidades de dominio anemicas', () => {
348
+ const anemicEntityAst = {
349
+ type: 'ClassDeclaration',
350
+ id: { type: 'Identifier', name: 'OrderEntity' },
351
+ body: {
352
+ type: 'ClassBody',
353
+ body: [
354
+ { type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
355
+ { type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
356
+ {
357
+ type: 'ClassMethod',
358
+ kind: 'method',
359
+ key: { type: 'Identifier', name: 'getStatus' },
360
+ body: { type: 'BlockStatement', body: [] },
361
+ },
362
+ {
363
+ type: 'ClassMethod',
364
+ kind: 'method',
365
+ key: { type: 'Identifier', name: 'setStatus' },
366
+ body: { type: 'BlockStatement', body: [] },
367
+ },
368
+ ],
369
+ },
370
+ };
371
+ const richEntityAst = {
372
+ type: 'ClassDeclaration',
373
+ id: { type: 'Identifier', name: 'OrderEntity' },
374
+ body: {
375
+ type: 'ClassBody',
376
+ body: [
377
+ { type: 'ClassProperty', key: { type: 'Identifier', name: 'id' } },
378
+ { type: 'ClassProperty', key: { type: 'Identifier', name: 'status' } },
379
+ {
380
+ type: 'ClassMethod',
381
+ kind: 'method',
382
+ key: { type: 'Identifier', name: 'markAsPaid' },
383
+ body: { type: 'BlockStatement', body: [] },
384
+ },
385
+ ],
386
+ },
387
+ };
388
+
389
+ assert.equal(hasBackendAnemicDomainModel(anemicEntityAst), true);
390
+ assert.equal(hasBackendAnemicDomainModel(richEntityAst), false);
391
+ });
392
+
154
393
  test('hasEvalCall detecta eval directo y descarta member eval', () => {
155
394
  const directEvalAst = {
156
395
  type: 'CallExpression',
@@ -1653,3 +1892,384 @@ test('findNetworkCallWithoutErrorHandlingLines devuelve lineas de llamadas sin m
1653
1892
 
1654
1893
  assert.deepEqual(findNetworkCallWithoutErrorHandlingLines(ast), [12]);
1655
1894
  });
1895
+
1896
+ test('hasNestJsConstructorDependencyWithoutDecorator detecta dependencias constructor sin decorador NestJS', () => {
1897
+ const undecoratedProviderAst = {
1898
+ type: 'Program',
1899
+ body: [
1900
+ {
1901
+ type: 'ClassDeclaration',
1902
+ id: { type: 'Identifier', name: 'OrdersService' },
1903
+ loc: { start: { line: 7 }, end: { line: 15 } },
1904
+ body: {
1905
+ type: 'ClassBody',
1906
+ body: [
1907
+ {
1908
+ type: 'ClassMethod',
1909
+ kind: 'constructor',
1910
+ key: { type: 'Identifier', name: 'constructor' },
1911
+ params: [
1912
+ {
1913
+ type: 'Identifier',
1914
+ name: 'repository',
1915
+ typeAnnotation: {
1916
+ type: 'TSTypeAnnotation',
1917
+ typeAnnotation: {
1918
+ type: 'TSTypeReference',
1919
+ typeName: { type: 'Identifier', name: 'OrdersRepository' },
1920
+ },
1921
+ },
1922
+ },
1923
+ ],
1924
+ },
1925
+ ],
1926
+ },
1927
+ },
1928
+ ],
1929
+ };
1930
+ const decoratedProviderAst = {
1931
+ ...undecoratedProviderAst,
1932
+ body: [
1933
+ {
1934
+ ...(undecoratedProviderAst.body[0] as object),
1935
+ decorators: [
1936
+ {
1937
+ type: 'Decorator',
1938
+ expression: {
1939
+ type: 'CallExpression',
1940
+ callee: { type: 'Identifier', name: 'Injectable' },
1941
+ arguments: [],
1942
+ },
1943
+ },
1944
+ ],
1945
+ },
1946
+ ],
1947
+ };
1948
+ const primitiveConstructorAst = {
1949
+ type: 'Program',
1950
+ body: [
1951
+ {
1952
+ type: 'ClassDeclaration',
1953
+ id: { type: 'Identifier', name: 'PrimitiveConfig' },
1954
+ body: {
1955
+ type: 'ClassBody',
1956
+ body: [
1957
+ {
1958
+ type: 'ClassMethod',
1959
+ kind: 'constructor',
1960
+ key: { type: 'Identifier', name: 'constructor' },
1961
+ params: [
1962
+ {
1963
+ type: 'Identifier',
1964
+ name: 'name',
1965
+ typeAnnotation: {
1966
+ type: 'TSTypeAnnotation',
1967
+ typeAnnotation: { type: 'TSStringKeyword' },
1968
+ },
1969
+ },
1970
+ ],
1971
+ },
1972
+ ],
1973
+ },
1974
+ },
1975
+ ],
1976
+ };
1977
+
1978
+ assert.equal(hasNestJsConstructorDependencyWithoutDecorator(undecoratedProviderAst), true);
1979
+ assert.equal(hasNestJsConstructorDependencyWithoutDecorator(decoratedProviderAst), false);
1980
+ assert.equal(hasNestJsConstructorDependencyWithoutDecorator(primitiveConstructorAst), false);
1981
+
1982
+ const match = findNestJsConstructorDependencyWithoutDecoratorMatch(undecoratedProviderAst);
1983
+ assert.ok(match);
1984
+ assert.deepEqual(match.lines, [7]);
1985
+ assert.equal(match.primary_node, 'OrdersService');
1986
+ });
1987
+
1988
+ test('hasPersistenceMutationWithoutAuditEvent detecta mutaciones persistentes sin evento ni auditoria', () => {
1989
+ const missingAuditAst = {
1990
+ type: 'Program',
1991
+ body: [
1992
+ {
1993
+ type: 'FunctionDeclaration',
1994
+ id: { type: 'Identifier', name: 'completeOrder' },
1995
+ loc: { start: { line: 21 }, end: { line: 25 } },
1996
+ body: {
1997
+ type: 'BlockStatement',
1998
+ body: [
1999
+ {
2000
+ type: 'ExpressionStatement',
2001
+ expression: {
2002
+ type: 'CallExpression',
2003
+ callee: {
2004
+ type: 'MemberExpression',
2005
+ object: { type: 'Identifier', name: 'ordersRepository' },
2006
+ property: { type: 'Identifier', name: 'save' },
2007
+ },
2008
+ arguments: [],
2009
+ },
2010
+ },
2011
+ ],
2012
+ },
2013
+ },
2014
+ ],
2015
+ };
2016
+ const auditedMutationAst = {
2017
+ type: 'Program',
2018
+ body: [
2019
+ {
2020
+ type: 'FunctionDeclaration',
2021
+ id: { type: 'Identifier', name: 'completeOrder' },
2022
+ body: {
2023
+ type: 'BlockStatement',
2024
+ body: [
2025
+ {
2026
+ type: 'ExpressionStatement',
2027
+ expression: {
2028
+ type: 'CallExpression',
2029
+ callee: {
2030
+ type: 'MemberExpression',
2031
+ object: { type: 'Identifier', name: 'ordersRepository' },
2032
+ property: { type: 'Identifier', name: 'save' },
2033
+ },
2034
+ arguments: [],
2035
+ },
2036
+ },
2037
+ {
2038
+ type: 'ExpressionStatement',
2039
+ expression: {
2040
+ type: 'CallExpression',
2041
+ callee: {
2042
+ type: 'MemberExpression',
2043
+ object: { type: 'Identifier', name: 'eventBus' },
2044
+ property: { type: 'Identifier', name: 'emit' },
2045
+ },
2046
+ arguments: [],
2047
+ },
2048
+ },
2049
+ ],
2050
+ },
2051
+ },
2052
+ ],
2053
+ };
2054
+
2055
+ assert.equal(hasPersistenceMutationWithoutAuditEvent(missingAuditAst), true);
2056
+ assert.equal(hasPersistenceMutationWithoutAuditEvent(auditedMutationAst), false);
2057
+
2058
+ const match = findPersistenceMutationWithoutAuditEventMatch(missingAuditAst);
2059
+ assert.ok(match);
2060
+ assert.deepEqual(match.lines, [21]);
2061
+ assert.equal(match.primary_node, 'completeOrder');
2062
+ });
2063
+
2064
+ test('hasDtoPropertyWithoutValidation detecta DTOs sin validacion class-validator', () => {
2065
+ const missingValidationAst = {
2066
+ type: 'Program',
2067
+ body: [
2068
+ {
2069
+ type: 'ClassDeclaration',
2070
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
2071
+ body: {
2072
+ type: 'ClassBody',
2073
+ body: [
2074
+ {
2075
+ type: 'ClassProperty',
2076
+ key: { type: 'Identifier', name: 'email' },
2077
+ loc: { start: { line: 4 }, end: { line: 4 } },
2078
+ typeAnnotation: { type: 'TSTypeAnnotation' },
2079
+ },
2080
+ ],
2081
+ },
2082
+ },
2083
+ ],
2084
+ };
2085
+ const validatedDtoAst = {
2086
+ type: 'Program',
2087
+ body: [
2088
+ {
2089
+ type: 'ClassDeclaration',
2090
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
2091
+ body: {
2092
+ type: 'ClassBody',
2093
+ body: [
2094
+ {
2095
+ type: 'ClassProperty',
2096
+ key: { type: 'Identifier', name: 'email' },
2097
+ decorators: [
2098
+ {
2099
+ type: 'Decorator',
2100
+ expression: {
2101
+ type: 'CallExpression',
2102
+ callee: { type: 'Identifier', name: 'IsEmail' },
2103
+ arguments: [],
2104
+ },
2105
+ },
2106
+ ],
2107
+ typeAnnotation: { type: 'TSTypeAnnotation' },
2108
+ },
2109
+ ],
2110
+ },
2111
+ },
2112
+ ],
2113
+ };
2114
+
2115
+ assert.equal(hasDtoPropertyWithoutValidation(missingValidationAst), true);
2116
+ assert.equal(hasDtoPropertyWithoutValidation(validatedDtoAst), false);
2117
+
2118
+ const match = findDtoPropertyWithoutValidationMatch(missingValidationAst);
2119
+ assert.ok(match);
2120
+ assert.deepEqual(match.lines, [4]);
2121
+ assert.equal(match.primary_node, 'CreateOrderDto');
2122
+ assert.deepEqual(match.related_nodes, ['email']);
2123
+ });
2124
+
2125
+ test('hasDtoNestedPropertyWithoutNestedValidation detecta DTOs anidados sin ValidateNested y Type', () => {
2126
+ const missingNestedValidationAst = {
2127
+ type: 'Program',
2128
+ body: [
2129
+ {
2130
+ type: 'ClassDeclaration',
2131
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
2132
+ body: {
2133
+ type: 'ClassBody',
2134
+ body: [
2135
+ {
2136
+ type: 'ClassProperty',
2137
+ key: { type: 'Identifier', name: 'address' },
2138
+ loc: { start: { line: 9 }, end: { line: 9 } },
2139
+ typeAnnotation: {
2140
+ type: 'TSTypeAnnotation',
2141
+ typeAnnotation: {
2142
+ type: 'TSTypeReference',
2143
+ typeName: { type: 'Identifier', name: 'AddressDto' },
2144
+ },
2145
+ },
2146
+ },
2147
+ ],
2148
+ },
2149
+ },
2150
+ ],
2151
+ };
2152
+ const validNestedDtoAst = {
2153
+ type: 'Program',
2154
+ body: [
2155
+ {
2156
+ type: 'ClassDeclaration',
2157
+ id: { type: 'Identifier', name: 'CreateOrderDto' },
2158
+ body: {
2159
+ type: 'ClassBody',
2160
+ body: [
2161
+ {
2162
+ type: 'ClassProperty',
2163
+ key: { type: 'Identifier', name: 'address' },
2164
+ decorators: [
2165
+ {
2166
+ type: 'Decorator',
2167
+ expression: {
2168
+ type: 'CallExpression',
2169
+ callee: { type: 'Identifier', name: 'ValidateNested' },
2170
+ arguments: [],
2171
+ },
2172
+ },
2173
+ {
2174
+ type: 'Decorator',
2175
+ expression: {
2176
+ type: 'CallExpression',
2177
+ callee: { type: 'Identifier', name: 'Type' },
2178
+ arguments: [],
2179
+ },
2180
+ },
2181
+ ],
2182
+ typeAnnotation: {
2183
+ type: 'TSTypeAnnotation',
2184
+ typeAnnotation: {
2185
+ type: 'TSTypeReference',
2186
+ typeName: { type: 'Identifier', name: 'AddressDto' },
2187
+ },
2188
+ },
2189
+ },
2190
+ ],
2191
+ },
2192
+ },
2193
+ ],
2194
+ };
2195
+
2196
+ assert.equal(hasDtoNestedPropertyWithoutNestedValidation(missingNestedValidationAst), true);
2197
+ assert.equal(hasDtoNestedPropertyWithoutNestedValidation(validNestedDtoAst), false);
2198
+
2199
+ const match = findDtoNestedPropertyWithoutNestedValidationMatch(missingNestedValidationAst);
2200
+ assert.ok(match);
2201
+ assert.deepEqual(match.lines, [9]);
2202
+ assert.equal(match.primary_node, 'CreateOrderDto');
2203
+ assert.deepEqual(match.related_nodes, ['address']);
2204
+ });
2205
+
2206
+ test('hasBackendControllerRouteWithoutGuard detecta rutas NestJS sin UseGuards', () => {
2207
+ const unguardedRouteAst = {
2208
+ type: 'Program',
2209
+ body: [
2210
+ {
2211
+ type: 'ClassDeclaration',
2212
+ decorators: [
2213
+ {
2214
+ type: 'Decorator',
2215
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
2216
+ },
2217
+ ],
2218
+ body: {
2219
+ type: 'ClassBody',
2220
+ body: [
2221
+ {
2222
+ type: 'ClassMethod',
2223
+ key: { type: 'Identifier', name: 'listOrders' },
2224
+ loc: { start: { line: 12 }, end: { line: 14 } },
2225
+ decorators: [
2226
+ {
2227
+ type: 'Decorator',
2228
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Get' }, arguments: [] },
2229
+ },
2230
+ ],
2231
+ },
2232
+ ],
2233
+ },
2234
+ },
2235
+ ],
2236
+ };
2237
+ const classGuardedRouteAst = {
2238
+ type: 'Program',
2239
+ body: [
2240
+ {
2241
+ type: 'ClassDeclaration',
2242
+ decorators: [
2243
+ {
2244
+ type: 'Decorator',
2245
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Controller' }, arguments: [] },
2246
+ },
2247
+ {
2248
+ type: 'Decorator',
2249
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'UseGuards' }, arguments: [] },
2250
+ },
2251
+ ],
2252
+ body: {
2253
+ type: 'ClassBody',
2254
+ body: [
2255
+ {
2256
+ type: 'ClassMethod',
2257
+ key: { type: 'Identifier', name: 'listOrders' },
2258
+ loc: { start: { line: 22 }, end: { line: 24 } },
2259
+ decorators: [
2260
+ {
2261
+ type: 'Decorator',
2262
+ expression: { type: 'CallExpression', callee: { type: 'Identifier', name: 'Get' }, arguments: [] },
2263
+ },
2264
+ ],
2265
+ },
2266
+ ],
2267
+ },
2268
+ },
2269
+ ],
2270
+ };
2271
+
2272
+ assert.equal(hasBackendControllerRouteWithoutGuard(unguardedRouteAst), true);
2273
+ assert.equal(hasBackendControllerRouteWithoutGuard(classGuardedRouteAst), false);
2274
+ assert.deepEqual(findBackendControllerRouteWithoutGuardLines(unguardedRouteAst), [12]);
2275
+ });