Skip to content

Commit 705b4da

Browse files
committed
[APPS] Resolve same-module backend connection IDs
1 parent 5c281d2 commit 705b4da

2 files changed

Lines changed: 514 additions & 34 deletions

File tree

packages/plugins/apps/src/backend/extract-connection-ids.test.ts

Lines changed: 223 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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',
@@ -231,41 +318,153 @@ describe('Backend Functions - extractConnectionIds', () => {
231318

232319
test.each([
233320
{
234-
description: 'identifier',
235-
expression: 'CONNECTION_ID',
236-
expectedType: 'Identifier',
321+
description: 'mutable let bindings',
322+
code: `
323+
import { request } from '@datadog/action-catalog/http/http';
324+
let CONNECTION_ID = 'mutable-let';
325+
request({ connectionId: CONNECTION_ID });
326+
`,
327+
expected: "declared with 'let'",
328+
},
329+
{
330+
description: 'mutable var bindings',
331+
code: `
332+
import { request } from '@datadog/action-catalog/http/http';
333+
var CONNECTION_ID = 'mutable-var';
334+
request({ connectionId: CONNECTION_ID });
335+
`,
336+
expected: "declared with 'var'",
337+
},
338+
{
339+
description: 'unresolved identifiers',
340+
code: `
341+
import { request } from '@datadog/action-catalog/http/http';
342+
request({ connectionId: CONNECTION_ID });
343+
`,
344+
expected: "identifier 'CONNECTION_ID' is not a top-level same-file const binding",
345+
},
346+
{
347+
description: 'destructured connection bindings',
348+
code: `
349+
import { request } from '@datadog/action-catalog/http/http';
350+
const CONNECTIONS = { HTTP: 'destructured-connection-binding' };
351+
const { HTTP } = CONNECTIONS;
352+
request({ connectionId: HTTP });
353+
`,
354+
expected: "identifier 'HTTP' is not a top-level same-file const binding",
355+
},
356+
{
357+
description: 'imported identifiers',
358+
code: `
359+
import { request } from '@datadog/action-catalog/http/http';
360+
import { CONNECTION_ID } from './connections';
361+
request({ connectionId: CONNECTION_ID });
362+
`,
363+
expected: "imported identifier 'CONNECTION_ID' cannot be statically analyzed",
364+
},
365+
{
366+
description: 'imported object members',
367+
code: `
368+
import { request } from '@datadog/action-catalog/http/http';
369+
import { CONNECTIONS } from './connections';
370+
request({ connectionId: CONNECTIONS.HTTP });
371+
`,
372+
expected: "imported object 'CONNECTIONS' cannot be statically analyzed",
373+
},
374+
{
375+
description: 'dynamic template literals',
376+
code: `
377+
import { request } from '@datadog/action-catalog/http/http';
378+
const prefix = 'conn';
379+
request({ connectionId: \`\${prefix}-dynamic\` });
380+
`,
381+
expected: 'template literals with interpolations cannot be statically analyzed',
237382
},
238383
{
239-
description: 'template literal',
240-
expression: '`conn-template`',
241-
expectedType: 'TemplateLiteral',
384+
description: 'binary expressions',
385+
code: `
386+
import { request } from '@datadog/action-catalog/http/http';
387+
request({ connectionId: 'conn-' + suffix });
388+
`,
389+
expected: 'got BinaryExpression',
242390
},
243391
{
244-
description: 'member expression',
245-
expression: 'CONNECTIONS.HTTP',
246-
expectedType: 'MemberExpression',
392+
description: 'function calls',
393+
code: `
394+
import { request } from '@datadog/action-catalog/http/http';
395+
request({ connectionId: getConnectionId() });
396+
`,
397+
expected: 'got CallExpression',
247398
},
248399
{
249-
description: 'call expression',
250-
expression: 'getConnectionId()',
251-
expectedType: 'CallExpression',
400+
description: 'env reads',
401+
code: `
402+
import { request } from '@datadog/action-catalog/http/http';
403+
request({ connectionId: process.env.CONNECTION_ID });
404+
`,
405+
expected: 'nested or non-static member expressions cannot be statically analyzed',
252406
},
253407
{
254-
description: 'binary expression',
255-
expression: "'conn-' + suffix",
256-
expectedType: 'BinaryExpression',
408+
description: 'computed object properties',
409+
code: `
410+
import { request } from '@datadog/action-catalog/http/http';
411+
const key = 'HTTP';
412+
const CONNECTIONS = { [key]: 'computed-object-property' };
413+
request({ connectionId: CONNECTIONS.HTTP });
414+
`,
415+
expected: 'computed object properties can hide connectionId object members',
416+
},
417+
{
418+
description: 'object spreads',
419+
code: `
420+
import { request } from '@datadog/action-catalog/http/http';
421+
const BASE = { HTTP: 'spread-object' };
422+
const CONNECTIONS = { ...BASE };
423+
request({ connectionId: CONNECTIONS.HTTP });
424+
`,
425+
expected: 'object spreads can hide connectionId object members',
426+
},
427+
{
428+
description: 'nested member chains',
429+
code: `
430+
import { request } from '@datadog/action-catalog/http/http';
431+
const CONNECTIONS = { HTTP: { PROD: 'nested-member-chain' } };
432+
request({ connectionId: CONNECTIONS.HTTP.PROD });
433+
`,
434+
expected: 'nested or non-static member expressions cannot be statically analyzed',
435+
},
436+
{
437+
description: 'computed member reads',
438+
code: `
439+
import { request } from '@datadog/action-catalog/http/http';
440+
const CONNECTIONS = { HTTP: 'computed-member-read' };
441+
request({ connectionId: CONNECTIONS['HTTP'] });
442+
`,
443+
expected: 'computed member expressions cannot be statically analyzed',
444+
},
445+
{
446+
description: 'object members missing a static property',
447+
code: `
448+
import { request } from '@datadog/action-catalog/http/http';
449+
const CONNECTIONS = { SLACK: 'slack-connection' };
450+
request({ connectionId: CONNECTIONS.HTTP });
451+
`,
452+
expected: "object has no static 'HTTP' property",
453+
},
454+
{
455+
description: 'const object aliases',
456+
code: `
457+
import { request } from '@datadog/action-catalog/http/http';
458+
const BASE = { HTTP: 'aliased-object' };
459+
const CONNECTIONS = BASE;
460+
request({ connectionId: CONNECTIONS.HTTP });
461+
`,
462+
expected: "object 'CONNECTIONS' must be initialized to an object literal",
257463
},
258464
])(
259465
'Should fail closed for unsupported connectionId value expressions: $description',
260-
({ expression, expectedType }) => {
261-
const ast = parseModule(`
262-
import { request } from '@datadog/action-catalog/http/http';
263-
request({ connectionId: ${expression} });
264-
`);
265-
266-
expect(() => extractConnectionIds(ast, filePath)).toThrow(
267-
`expected an inline string literal, got ${expectedType}`,
268-
);
466+
({ code, expected }) => {
467+
expect(() => extractConnectionIds(parseModule(code), filePath)).toThrow(expected);
269468
},
270469
);
271470
});

0 commit comments

Comments
 (0)