@@ -115,6 +115,93 @@ describe('Backend Functions - extractConnectionIds', () => {
115115 expect ( extractConnectionIds ( ast , filePath ) ) . toEqual ( [ ] ) ;
116116 } ) ;
117117
118+ // This extractor receives the ESTree Program from Rollup's parser; TS-only
119+ // syntax such as `as const` is outside this helper's parser boundary.
120+ test . each ( [
121+ {
122+ description : 'same-file const string identifiers' ,
123+ code : `
124+ import { request } from '@datadog/action-catalog/http/http';
125+ const CONNECTION_ID = 'same-file-const';
126+ request({ connectionId: CONNECTION_ID });
127+ ` ,
128+ expected : [ 'same-file-const' ] ,
129+ } ,
130+ {
131+ description : 'exported same-file const string identifiers' ,
132+ code : `
133+ import { request } from '@datadog/action-catalog/http/http';
134+ export const CONNECTION_ID = 'exported-same-file-const';
135+ request({ connectionId: CONNECTION_ID });
136+ ` ,
137+ expected : [ 'exported-same-file-const' ] ,
138+ } ,
139+ {
140+ description : 'same-file const-to-const chains' ,
141+ code : `
142+ import { request } from '@datadog/action-catalog/http/http';
143+ const A = 'const-chain';
144+ const B = A;
145+ const C = B;
146+ request({ connectionId: C });
147+ ` ,
148+ expected : [ 'const-chain' ] ,
149+ } ,
150+ {
151+ description : 'inline static template literals' ,
152+ code : `
153+ import { request } from '@datadog/action-catalog/http/http';
154+ request({ connectionId: \`inline-static-template\` });
155+ ` ,
156+ expected : [ 'inline-static-template' ] ,
157+ } ,
158+ {
159+ description : 'same-file const static template literals' ,
160+ code : `
161+ import { request } from '@datadog/action-catalog/http/http';
162+ const CONNECTION_ID = \`const-static-template\`;
163+ request({ connectionId: CONNECTION_ID });
164+ ` ,
165+ expected : [ 'const-static-template' ] ,
166+ } ,
167+ {
168+ description : 'same-file const object members with identifier keys' ,
169+ code : `
170+ import { request } from '@datadog/action-catalog/http/http';
171+ const CONNECTIONS = {
172+ HTTP: 'object-identifier-key',
173+ };
174+ request({ connectionId: CONNECTIONS.HTTP });
175+ ` ,
176+ expected : [ 'object-identifier-key' ] ,
177+ } ,
178+ {
179+ description : 'same-file const object members with string-literal keys' ,
180+ code : `
181+ import { request } from '@datadog/action-catalog/http/http';
182+ const CONNECTIONS = {
183+ 'HTTP': 'object-string-key',
184+ };
185+ request({ connectionId: CONNECTIONS.HTTP });
186+ ` ,
187+ expected : [ 'object-string-key' ] ,
188+ } ,
189+ {
190+ description : 'same-file const object members whose values are const identifiers' ,
191+ code : `
192+ import { request } from '@datadog/action-catalog/http/http';
193+ const HTTP_CONNECTION_ID = 'object-const-value';
194+ const CONNECTIONS = {
195+ HTTP: HTTP_CONNECTION_ID,
196+ };
197+ request({ connectionId: CONNECTIONS.HTTP });
198+ ` ,
199+ expected : [ 'object-const-value' ] ,
200+ } ,
201+ ] ) ( 'Should resolve $description' , ( { code, expected } ) => {
202+ expect ( extractConnectionIds ( parseModule ( code ) , filePath ) ) . toEqual ( expected ) ;
203+ } ) ;
204+
118205 test . each ( [
119206 {
120207 description : 'non-object first arguments' ,
@@ -183,41 +270,153 @@ describe('Backend Functions - extractConnectionIds', () => {
183270
184271 test . each ( [
185272 {
186- description : 'identifier' ,
187- expression : 'CONNECTION_ID' ,
188- expectedType : 'Identifier' ,
273+ description : 'mutable let bindings' ,
274+ code : `
275+ import { request } from '@datadog/action-catalog/http/http';
276+ let CONNECTION_ID = 'mutable-let';
277+ request({ connectionId: CONNECTION_ID });
278+ ` ,
279+ expected : "declared with 'let'" ,
280+ } ,
281+ {
282+ description : 'mutable var bindings' ,
283+ code : `
284+ import { request } from '@datadog/action-catalog/http/http';
285+ var CONNECTION_ID = 'mutable-var';
286+ request({ connectionId: CONNECTION_ID });
287+ ` ,
288+ expected : "declared with 'var'" ,
289+ } ,
290+ {
291+ description : 'unresolved identifiers' ,
292+ code : `
293+ import { request } from '@datadog/action-catalog/http/http';
294+ request({ connectionId: CONNECTION_ID });
295+ ` ,
296+ expected : "identifier 'CONNECTION_ID' is not a top-level same-file const binding" ,
297+ } ,
298+ {
299+ description : 'destructured connection bindings' ,
300+ code : `
301+ import { request } from '@datadog/action-catalog/http/http';
302+ const CONNECTIONS = { HTTP: 'destructured-connection-binding' };
303+ const { HTTP } = CONNECTIONS;
304+ request({ connectionId: HTTP });
305+ ` ,
306+ expected : "identifier 'HTTP' is not a top-level same-file const binding" ,
307+ } ,
308+ {
309+ description : 'imported identifiers' ,
310+ code : `
311+ import { request } from '@datadog/action-catalog/http/http';
312+ import { CONNECTION_ID } from './connections';
313+ request({ connectionId: CONNECTION_ID });
314+ ` ,
315+ expected : "imported identifier 'CONNECTION_ID' cannot be statically analyzed" ,
316+ } ,
317+ {
318+ description : 'imported object members' ,
319+ code : `
320+ import { request } from '@datadog/action-catalog/http/http';
321+ import { CONNECTIONS } from './connections';
322+ request({ connectionId: CONNECTIONS.HTTP });
323+ ` ,
324+ expected : "imported object 'CONNECTIONS' cannot be statically analyzed" ,
325+ } ,
326+ {
327+ description : 'dynamic template literals' ,
328+ code : `
329+ import { request } from '@datadog/action-catalog/http/http';
330+ const prefix = 'conn';
331+ request({ connectionId: \`\${prefix}-dynamic\` });
332+ ` ,
333+ expected : 'template literals with interpolations cannot be statically analyzed' ,
189334 } ,
190335 {
191- description : 'template literal' ,
192- expression : '`conn-template`' ,
193- expectedType : 'TemplateLiteral' ,
336+ description : 'binary expressions' ,
337+ code : `
338+ import { request } from '@datadog/action-catalog/http/http';
339+ request({ connectionId: 'conn-' + suffix });
340+ ` ,
341+ expected : 'got BinaryExpression' ,
194342 } ,
195343 {
196- description : 'member expression' ,
197- expression : 'CONNECTIONS.HTTP' ,
198- expectedType : 'MemberExpression' ,
344+ description : 'function calls' ,
345+ code : `
346+ import { request } from '@datadog/action-catalog/http/http';
347+ request({ connectionId: getConnectionId() });
348+ ` ,
349+ expected : 'got CallExpression' ,
199350 } ,
200351 {
201- description : 'call expression' ,
202- expression : 'getConnectionId()' ,
203- expectedType : 'CallExpression' ,
352+ description : 'env reads' ,
353+ code : `
354+ import { request } from '@datadog/action-catalog/http/http';
355+ request({ connectionId: process.env.CONNECTION_ID });
356+ ` ,
357+ expected : 'nested or non-static member expressions cannot be statically analyzed' ,
204358 } ,
205359 {
206- description : 'binary expression' ,
207- expression : "'conn-' + suffix" ,
208- expectedType : 'BinaryExpression' ,
360+ description : 'computed object properties' ,
361+ code : `
362+ import { request } from '@datadog/action-catalog/http/http';
363+ const key = 'HTTP';
364+ const CONNECTIONS = { [key]: 'computed-object-property' };
365+ request({ connectionId: CONNECTIONS.HTTP });
366+ ` ,
367+ expected : 'computed object properties can hide connectionId object members' ,
368+ } ,
369+ {
370+ description : 'object spreads' ,
371+ code : `
372+ import { request } from '@datadog/action-catalog/http/http';
373+ const BASE = { HTTP: 'spread-object' };
374+ const CONNECTIONS = { ...BASE };
375+ request({ connectionId: CONNECTIONS.HTTP });
376+ ` ,
377+ expected : 'object spreads can hide connectionId object members' ,
378+ } ,
379+ {
380+ description : 'nested member chains' ,
381+ code : `
382+ import { request } from '@datadog/action-catalog/http/http';
383+ const CONNECTIONS = { HTTP: { PROD: 'nested-member-chain' } };
384+ request({ connectionId: CONNECTIONS.HTTP.PROD });
385+ ` ,
386+ expected : 'nested or non-static member expressions cannot be statically analyzed' ,
387+ } ,
388+ {
389+ description : 'computed member reads' ,
390+ code : `
391+ import { request } from '@datadog/action-catalog/http/http';
392+ const CONNECTIONS = { HTTP: 'computed-member-read' };
393+ request({ connectionId: CONNECTIONS['HTTP'] });
394+ ` ,
395+ expected : 'computed member expressions cannot be statically analyzed' ,
396+ } ,
397+ {
398+ description : 'object members missing a static property' ,
399+ code : `
400+ import { request } from '@datadog/action-catalog/http/http';
401+ const CONNECTIONS = { SLACK: 'slack-connection' };
402+ request({ connectionId: CONNECTIONS.HTTP });
403+ ` ,
404+ expected : "object has no static 'HTTP' property" ,
405+ } ,
406+ {
407+ description : 'const object aliases' ,
408+ code : `
409+ import { request } from '@datadog/action-catalog/http/http';
410+ const BASE = { HTTP: 'aliased-object' };
411+ const CONNECTIONS = BASE;
412+ request({ connectionId: CONNECTIONS.HTTP });
413+ ` ,
414+ expected : "object 'CONNECTIONS' must be initialized to an object literal" ,
209415 } ,
210416 ] ) (
211417 'Should fail closed for unsupported connectionId value expressions: $description' ,
212- ( { expression, expectedType } ) => {
213- const ast = parseModule ( `
214- import { request } from '@datadog/action-catalog/http/http';
215- request({ connectionId: ${ expression } });
216- ` ) ;
217-
218- expect ( ( ) => extractConnectionIds ( ast , filePath ) ) . toThrow (
219- `expected an inline string literal, got ${ expectedType } ` ,
220- ) ;
418+ ( { code, expected } ) => {
419+ expect ( ( ) => extractConnectionIds ( parseModule ( code ) , filePath ) ) . toThrow ( expected ) ;
221420 } ,
222421 ) ;
223422} ) ;
0 commit comments