Skip to content

Commit 8000ecd

Browse files
committed
[APPS] Resolve same-module backend connection IDs
1 parent b9320d3 commit 8000ecd

2 files changed

Lines changed: 485 additions & 32 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',
@@ -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

Comments
 (0)