dd-trace 5.41.1 → 5.42.0

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.
Files changed (59) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +8 -1
  3. package/package.json +6 -3
  4. package/packages/datadog-esbuild/index.js +3 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +37 -29
  6. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +102 -0
  7. package/packages/datadog-instrumentations/src/{check_require_cache.js → helpers/check-require-cache.js} +2 -2
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +4 -1
  10. package/packages/datadog-instrumentations/src/jest.js +72 -49
  11. package/packages/datadog-instrumentations/src/langchain.js +29 -10
  12. package/packages/datadog-instrumentations/src/mocha/main.js +53 -34
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +34 -24
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -8
  15. package/packages/datadog-instrumentations/src/openai.js +1 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +37 -30
  17. package/packages/datadog-instrumentations/src/vitest.js +64 -29
  18. package/packages/datadog-plugin-cucumber/src/index.js +13 -4
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +41 -35
  20. package/packages/datadog-plugin-cypress/src/plugin.js +10 -0
  21. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +195 -0
  22. package/packages/datadog-plugin-jest/src/index.js +18 -6
  23. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +4 -1
  24. package/packages/datadog-plugin-mocha/src/index.js +13 -4
  25. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  26. package/packages/datadog-plugin-vitest/src/index.js +41 -17
  27. package/packages/dd-trace/src/appsec/api_security_sampler.js +7 -3
  28. package/packages/dd-trace/src/appsec/blocking.js +23 -16
  29. package/packages/dd-trace/src/appsec/graphql.js +13 -6
  30. package/packages/dd-trace/src/appsec/rasp/utils.js +0 -1
  31. package/packages/dd-trace/src/appsec/reporter.js +35 -0
  32. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -3
  33. package/packages/dd-trace/src/appsec/telemetry/index.js +5 -1
  34. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -1
  35. package/packages/dd-trace/src/appsec/telemetry/waf.js +16 -1
  36. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +43 -13
  37. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  38. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -14
  39. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -2
  40. package/packages/dd-trace/src/ci-visibility/telemetry.js +2 -1
  41. package/packages/dd-trace/src/ci-visibility/{quarantined-tests/get-quarantined-tests.js → test-management/get-test-management-tests.js} +5 -5
  42. package/packages/dd-trace/src/config.js +11 -1
  43. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -2
  44. package/packages/dd-trace/src/lambda/runtime/patch.js +5 -3
  45. package/packages/dd-trace/src/lambda/runtime/ritm.js +13 -18
  46. package/packages/dd-trace/src/llmobs/plugins/openai.js +27 -2
  47. package/packages/dd-trace/src/opentracing/span.js +3 -0
  48. package/packages/dd-trace/src/plugins/ci_plugin.js +38 -10
  49. package/packages/dd-trace/src/plugins/index.js +1 -0
  50. package/packages/dd-trace/src/plugins/util/git.js +7 -3
  51. package/packages/dd-trace/src/plugins/util/test.js +10 -0
  52. package/packages/dd-trace/src/plugins/util/web.js +5 -2
  53. package/packages/dd-trace/src/priority_sampler.js +116 -15
  54. package/packages/dd-trace/src/sampler.js +9 -0
  55. package/packages/dd-trace/src/standalone/product.js +6 -2
  56. package/packages/dd-trace/src/startup-log.js +2 -1
  57. package/packages/dd-trace/src/telemetry/metrics.js +0 -8
  58. package/packages/dd-trace/src/tracer.js +1 -1
  59. /package/packages/datadog-instrumentations/src/{utils/src → helpers}/extract-package-and-module-path.js +0 -0
@@ -0,0 +1,195 @@
1
+ 'use strict'
2
+
3
+ const { MEASURED } = require('../../../ext/tags')
4
+ const { storage } = require('../../datadog-core')
5
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
6
+ const makeUtilities = require('../../dd-trace/src/plugins/util/llm')
7
+
8
+ class GoogleCloudVertexAIPlugin extends TracingPlugin {
9
+ static get id () { return 'google-cloud-vertexai' }
10
+ static get prefix () {
11
+ return 'tracing:apm:vertexai:request'
12
+ }
13
+
14
+ constructor () {
15
+ super(...arguments)
16
+
17
+ Object.assign(this, makeUtilities('vertexai', this._tracerConfig))
18
+ }
19
+
20
+ bindStart (ctx) {
21
+ const { instance, request, resource, stream } = ctx
22
+
23
+ const tags = this.tagRequest(request, instance, stream)
24
+
25
+ const span = this.startSpan('vertexai.request', {
26
+ service: this.config.service,
27
+ resource,
28
+ kind: 'client',
29
+ meta: {
30
+ [MEASURED]: 1,
31
+ ...tags
32
+ }
33
+ }, false)
34
+
35
+ const store = storage('legacy').getStore() || {}
36
+ ctx.currentStore = { ...store, span }
37
+
38
+ return ctx.currentStore
39
+ }
40
+
41
+ asyncEnd (ctx) {
42
+ const span = ctx.currentStore?.span
43
+ if (!span) return
44
+
45
+ const { result } = ctx
46
+
47
+ const response = result?.response
48
+ if (response) {
49
+ const tags = this.tagResponse(response)
50
+ span.addTags(tags)
51
+ }
52
+
53
+ span.finish()
54
+ }
55
+
56
+ tagRequest (request, instance, stream) {
57
+ const model = extractModel(instance)
58
+ const tags = {
59
+ 'vertexai.request.model': model
60
+ }
61
+
62
+ const history = instance.historyInternal
63
+ let contents = typeof request === 'string' || Array.isArray(request) ? request : request.contents
64
+ if (history) {
65
+ contents = [...history, ...(Array.isArray(contents) ? contents : [contents])]
66
+ }
67
+
68
+ const generationConfig = instance.generationConfig || {}
69
+ for (const key of Object.keys(generationConfig)) {
70
+ const transformedKey = key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()
71
+ tags[`vertexai.request.generation_config.${transformedKey}`] = JSON.stringify(generationConfig[key])
72
+ }
73
+
74
+ if (stream) {
75
+ tags['vertexai.request.stream'] = true
76
+ }
77
+
78
+ if (!this.isPromptCompletionSampled()) return tags
79
+
80
+ const systemInstructions = extractSystemInstructions(instance)
81
+
82
+ for (const [idx, systemInstruction] of systemInstructions.entries()) {
83
+ tags[`vertexai.request.system_instruction.${idx}.text`] = systemInstruction
84
+ }
85
+
86
+ if (typeof contents === 'string') {
87
+ tags['vertexai.request.contents.0.text'] = contents
88
+ return tags
89
+ }
90
+
91
+ for (const [contentIdx, content] of contents.entries()) {
92
+ this.tagRequestContent(tags, content, contentIdx)
93
+ }
94
+
95
+ return tags
96
+ }
97
+
98
+ tagRequestPart (part, tags, partIdx, contentIdx) {
99
+ tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.text`] = this.normalize(part.text)
100
+
101
+ const functionCall = part.functionCall
102
+ const functionResponse = part.functionResponse
103
+
104
+ if (functionCall) {
105
+ tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_call.name`] = functionCall.name
106
+ tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_call.args`] =
107
+ this.normalize(JSON.stringify(functionCall.args))
108
+ }
109
+ if (functionResponse) {
110
+ tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_response.name`] =
111
+ functionResponse.name
112
+ tags[`vertexai.request.contents.${contentIdx}.parts.${partIdx}.function_response.response`] =
113
+ this.normalize(JSON.stringify(functionResponse.response))
114
+ }
115
+ }
116
+
117
+ tagRequestContent (tags, content, contentIdx) {
118
+ if (typeof content === 'string') {
119
+ tags[`vertexai.request.contents.${contentIdx}.text`] = this.normalize(content)
120
+ return
121
+ }
122
+
123
+ if (content.text || content.functionCall || content.functionResponse) {
124
+ this.tagRequestPart(content, tags, 0, contentIdx)
125
+ return
126
+ }
127
+
128
+ const { role, parts } = content
129
+ if (role) {
130
+ tags[`vertexai.request.contents.${contentIdx}.role`] = role
131
+ }
132
+
133
+ for (const [partIdx, part] of parts.entries()) {
134
+ this.tagRequestPart(part, tags, partIdx, contentIdx)
135
+ }
136
+ }
137
+
138
+ tagResponse (response) {
139
+ const tags = {}
140
+
141
+ const candidates = response.candidates
142
+ for (const [candidateIdx, candidate] of candidates.entries()) {
143
+ const finishReason = candidate.finishReason
144
+ if (finishReason) {
145
+ tags[`vertexai.response.candidates.${candidateIdx}.finish_reason`] = finishReason
146
+ }
147
+ const candidateContent = candidate.content
148
+ const role = candidateContent.role
149
+ tags[`vertexai.response.candidates.${candidateIdx}.content.role`] = role
150
+
151
+ if (!this.isPromptCompletionSampled()) continue
152
+
153
+ const parts = candidateContent.parts
154
+ for (const [partIdx, part] of parts.entries()) {
155
+ const text = part.text
156
+ tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.text`] =
157
+ this.normalize(String(text))
158
+
159
+ const functionCall = part.functionCall
160
+ if (!functionCall) continue
161
+
162
+ tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.function_call.name`] =
163
+ functionCall.name
164
+ tags[`vertexai.response.candidates.${candidateIdx}.content.parts.${partIdx}.function_call.args`] =
165
+ this.normalize(JSON.stringify(functionCall.args))
166
+ }
167
+ }
168
+
169
+ const tokenCounts = response.usageMetadata
170
+ if (tokenCounts) {
171
+ tags['vertexai.response.usage.prompt_tokens'] = tokenCounts.promptTokenCount
172
+ tags['vertexai.response.usage.completion_tokens'] = tokenCounts.candidatesTokenCount
173
+ tags['vertexai.response.usage.total_tokens'] = tokenCounts.totalTokenCount
174
+ }
175
+
176
+ return tags
177
+ }
178
+ }
179
+
180
+ function extractModel (instance) {
181
+ const model = instance.model || instance.resourcePath || instance.publisherModelEndpoint
182
+ return model?.split('/').pop()
183
+ }
184
+
185
+ function extractSystemInstructions (instance) {
186
+ // systemInstruction is either a string or a Content object
187
+ // Content objects have parts (Part[]) and a role
188
+ const systemInstruction = instance.systemInstruction
189
+ if (!systemInstruction) return []
190
+ if (typeof systemInstruction === 'string') return [systemInstruction]
191
+
192
+ return systemInstruction.parts?.map(part => part.text)
193
+ }
194
+
195
+ module.exports = GoogleCloudVertexAIPlugin
@@ -26,7 +26,8 @@ const {
26
26
  getFormattedError,
27
27
  TEST_RETRY_REASON,
28
28
  TEST_MANAGEMENT_ENABLED,
29
- TEST_MANAGEMENT_IS_QUARANTINED
29
+ TEST_MANAGEMENT_IS_QUARANTINED,
30
+ TEST_MANAGEMENT_IS_DISABLED
30
31
  } = require('../../dd-trace/src/plugins/util/test')
31
32
  const { COMPONENT } = require('../../dd-trace/src/constants')
32
33
  const id = require('../../dd-trace/src/id')
@@ -108,7 +109,7 @@ class JestPlugin extends CiPlugin {
108
109
  error,
109
110
  isEarlyFlakeDetectionEnabled,
110
111
  isEarlyFlakeDetectionFaulty,
111
- isQuarantinedTestsEnabled,
112
+ isTestManagementTestsEnabled,
112
113
  onDone
113
114
  }) => {
114
115
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -140,7 +141,7 @@ class JestPlugin extends CiPlugin {
140
141
  if (isEarlyFlakeDetectionFaulty) {
141
142
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
142
143
  }
143
- if (isQuarantinedTestsEnabled) {
144
+ if (isTestManagementTestsEnabled) {
144
145
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
145
146
  }
146
147
 
@@ -150,7 +151,10 @@ class JestPlugin extends CiPlugin {
150
151
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
151
152
  finishAllTraceSpans(this.testSessionSpan)
152
153
 
153
- this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
154
+ this.telemetry.count(TELEMETRY_TEST_SESSION, {
155
+ provider: this.ciProviderName,
156
+ autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
157
+ })
154
158
 
155
159
  this.tracer._exporter.flush(() => {
156
160
  if (onDone) {
@@ -172,7 +176,7 @@ class JestPlugin extends CiPlugin {
172
176
  config._ddEarlyFlakeDetectionNumRetries = this.libraryConfig?.earlyFlakeDetectionNumRetries ?? 0
173
177
  config._ddRepositoryRoot = this.repositoryRoot
174
178
  config._ddIsFlakyTestRetriesEnabled = this.libraryConfig?.isFlakyTestRetriesEnabled ?? false
175
- config._ddIsQuarantinedTestsEnabled = this.libraryConfig?.isQuarantinedTestsEnabled ?? false
179
+ config._ddIsTestManagementTestsEnabled = this.libraryConfig?.isTestManagementEnabled ?? false
176
180
  config._ddFlakyTestRetriesCount = this.libraryConfig?.flakyTestRetriesCount
177
181
  config._ddIsDiEnabled = this.libraryConfig?.isDiEnabled ?? false
178
182
  config._ddIsKnownTestsEnabled = this.libraryConfig?.isKnownTestsEnabled ?? false
@@ -377,9 +381,17 @@ class JestPlugin extends CiPlugin {
377
381
  }
378
382
  })
379
383
 
380
- this.addSub('ci:jest:test:skip', (test) => {
384
+ this.addSub('ci:jest:test:skip', ({
385
+ test,
386
+ isDisabled
387
+ }) => {
381
388
  const span = this.startTestSpan(test)
382
389
  span.setTag(TEST_STATUS, 'skip')
390
+
391
+ if (isDisabled) {
392
+ span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
393
+ }
394
+
383
395
  span.finish()
384
396
  })
385
397
  }
@@ -43,7 +43,10 @@ class LangChainEmbeddingHandler extends LangChainHandler {
43
43
  }
44
44
 
45
45
  extractApiKey (instance) {
46
- const apiKey = instance.clientConfig?.apiKey
46
+ const apiKey =
47
+ instance.clientConfig?.apiKey ||
48
+ instance.apiKey ||
49
+ instance.client?.apiKey
47
50
  if (!apiKey || apiKey.length < 4) return ''
48
51
  return `...${apiKey.slice(-4)}`
49
52
  }
@@ -33,7 +33,8 @@ const {
33
33
  TEST_BROWSER_DRIVER,
34
34
  TEST_RETRY_REASON,
35
35
  TEST_MANAGEMENT_ENABLED,
36
- TEST_MANAGEMENT_IS_QUARANTINED
36
+ TEST_MANAGEMENT_IS_QUARANTINED,
37
+ TEST_MANAGEMENT_IS_DISABLED
37
38
  } = require('../../dd-trace/src/plugins/util/test')
38
39
  const { COMPONENT } = require('../../dd-trace/src/constants')
39
40
  const {
@@ -311,7 +312,7 @@ class MochaPlugin extends CiPlugin {
311
312
  error,
312
313
  isEarlyFlakeDetectionEnabled,
313
314
  isEarlyFlakeDetectionFaulty,
314
- isQuarantinedTestsEnabled,
315
+ isTestManagementEnabled,
315
316
  isParallel
316
317
  }) => {
317
318
  if (this.testSessionSpan) {
@@ -328,7 +329,7 @@ class MochaPlugin extends CiPlugin {
328
329
  this.testSessionSpan.setTag(MOCHA_IS_PARALLEL, 'true')
329
330
  }
330
331
 
331
- if (isQuarantinedTestsEnabled) {
332
+ if (isTestManagementEnabled) {
332
333
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
333
334
  }
334
335
 
@@ -359,7 +360,10 @@ class MochaPlugin extends CiPlugin {
359
360
  this.testSessionSpan.finish()
360
361
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
361
362
  finishAllTraceSpans(this.testSessionSpan)
362
- this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
363
+ this.telemetry.count(TELEMETRY_TEST_SESSION, {
364
+ provider: this.ciProviderName,
365
+ autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
366
+ })
363
367
  }
364
368
  this.libraryConfig = null
365
369
  this.tracer._exporter.flush()
@@ -405,6 +409,7 @@ class MochaPlugin extends CiPlugin {
405
409
  isEfdRetry,
406
410
  testStartLine,
407
411
  isParallel,
412
+ isDisabled,
408
413
  isQuarantined
409
414
  } = testInfo
410
415
 
@@ -424,6 +429,10 @@ class MochaPlugin extends CiPlugin {
424
429
  extraTags[MOCHA_IS_PARALLEL] = 'true'
425
430
  }
426
431
 
432
+ if (isDisabled) {
433
+ extraTags[TEST_MANAGEMENT_IS_DISABLED] = 'true'
434
+ }
435
+
427
436
  if (isQuarantined) {
428
437
  extraTags[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
429
438
  }
@@ -19,7 +19,8 @@ const {
19
19
  TEST_RETRY_REASON,
20
20
  TEST_MANAGEMENT_IS_QUARANTINED,
21
21
  TEST_MANAGEMENT_ENABLED,
22
- TEST_BROWSER_NAME
22
+ TEST_BROWSER_NAME,
23
+ TEST_MANAGEMENT_IS_DISABLED
23
24
  } = require('../../dd-trace/src/plugins/util/test')
24
25
  const { RESOURCE_NAME } = require('../../../ext/tags')
25
26
  const { COMPONENT } = require('../../dd-trace/src/constants')
@@ -44,7 +45,7 @@ class PlaywrightPlugin extends CiPlugin {
44
45
  this.addSub('ci:playwright:session:finish', ({
45
46
  status,
46
47
  isEarlyFlakeDetectionEnabled,
47
- isQuarantinedTestsEnabled,
48
+ isTestManagementTestsEnabled,
48
49
  onDone
49
50
  }) => {
50
51
  this.testModuleSpan.setTag(TEST_STATUS, status)
@@ -64,7 +65,7 @@ class PlaywrightPlugin extends CiPlugin {
64
65
  this.testSessionSpan.setTag('error', error)
65
66
  }
66
67
 
67
- if (isQuarantinedTestsEnabled) {
68
+ if (isTestManagementTestsEnabled) {
68
69
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
69
70
  }
70
71
 
@@ -73,7 +74,10 @@ class PlaywrightPlugin extends CiPlugin {
73
74
  this.testSessionSpan.finish()
74
75
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
75
76
  finishAllTraceSpans(this.testSessionSpan)
76
- this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
77
+ this.telemetry.count(TELEMETRY_TEST_SESSION, {
78
+ provider: this.ciProviderName,
79
+ autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
80
+ })
77
81
  appClosingTelemetry()
78
82
  this.tracer._exporter.flush(onDone)
79
83
  this.numFailedTests = 0
@@ -132,12 +136,22 @@ class PlaywrightPlugin extends CiPlugin {
132
136
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
133
137
  })
134
138
 
135
- this.addSub('ci:playwright:test:start', ({ testName, testSuiteAbsolutePath, testSourceLine, browserName }) => {
139
+ this.addSub('ci:playwright:test:start', ({
140
+ testName,
141
+ testSuiteAbsolutePath,
142
+ testSourceLine,
143
+ browserName,
144
+ isDisabled
145
+ }) => {
136
146
  const store = storage('legacy').getStore()
137
147
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.rootDir)
138
148
  const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
139
149
  const span = this.startTestSpan(testName, testSuite, testSourceFile, testSourceLine, browserName)
140
150
 
151
+ if (isDisabled) {
152
+ span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
153
+ }
154
+
141
155
  this.enter(span, store)
142
156
  })
143
157
  this.addSub('ci:playwright:test:finish', ({
@@ -20,7 +20,10 @@ const {
20
20
  TEST_EARLY_FLAKE_ABORT_REASON,
21
21
  TEST_RETRY_REASON,
22
22
  TEST_MANAGEMENT_ENABLED,
23
- TEST_MANAGEMENT_IS_QUARANTINED
23
+ TEST_MANAGEMENT_IS_QUARANTINED,
24
+ TEST_MANAGEMENT_IS_DISABLED,
25
+ DD_CAPABILITIES_EARLY_FLAKE_DETECTION,
26
+ DD_CAPABILITIES_AUTO_TEST_RETRIES
24
27
  } = require('../../dd-trace/src/plugins/util/test')
25
28
  const { COMPONENT } = require('../../dd-trace/src/constants')
26
29
  const {
@@ -50,16 +53,16 @@ class VitestPlugin extends CiPlugin {
50
53
  onDone(!testsForThisTestSuite.includes(testName))
51
54
  })
52
55
 
53
- this.addSub('ci:vitest:test:is-quarantined', ({ quarantinedTests, testSuiteAbsolutePath, testName, onDone }) => {
56
+ this.addSub('ci:vitest:test:is-disabled', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
54
57
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
55
- const isQuarantined = quarantinedTests
56
- ?.vitest
57
- ?.suites
58
- ?.[testSuite]
59
- ?.tests
60
- ?.[testName]
61
- ?.properties
62
- ?.quarantined
58
+ const { isDisabled } = this.getTestProperties(testManagementTests, testSuite, testName)
59
+
60
+ onDone(isDisabled ?? false)
61
+ })
62
+
63
+ this.addSub('ci:vitest:test:is-quarantined', ({ testManagementTests, testSuiteAbsolutePath, testName, onDone }) => {
64
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
65
+ const { isQuarantined } = this.getTestProperties(testManagementTests, testSuite, testName)
63
66
 
64
67
  onDone(isQuarantined ?? false)
65
68
  })
@@ -178,7 +181,7 @@ class VitestPlugin extends CiPlugin {
178
181
  }
179
182
  })
180
183
 
181
- this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew }) => {
184
+ this.addSub('ci:vitest:test:skip', ({ testName, testSuiteAbsolutePath, isNew, isDisabled }) => {
182
185
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
183
186
  const testSpan = this.startTestSpan(
184
187
  testName,
@@ -188,6 +191,7 @@ class VitestPlugin extends CiPlugin {
188
191
  [TEST_SOURCE_FILE]: testSuite,
189
192
  [TEST_SOURCE_START]: 1, // we can't get the proper start line in vitest
190
193
  [TEST_STATUS]: 'skip',
194
+ ...(isDisabled ? { [TEST_MANAGEMENT_IS_DISABLED]: 'true' } : {}),
191
195
  ...(isNew ? { [TEST_IS_NEW]: 'true' } : {})
192
196
  }
193
197
  )
@@ -197,7 +201,12 @@ class VitestPlugin extends CiPlugin {
197
201
  testSpan.finish()
198
202
  })
199
203
 
200
- this.addSub('ci:vitest:test-suite:start', ({ testSuiteAbsolutePath, frameworkVersion }) => {
204
+ this.addSub('ci:vitest:test-suite:start', ({
205
+ testSuiteAbsolutePath,
206
+ frameworkVersion,
207
+ isFlakyTestRetriesEnabled,
208
+ isEarlyFlakeDetectionEnabled
209
+ }) => {
201
210
  this.command = process.env.DD_CIVISIBILITY_TEST_COMMAND
202
211
  this.frameworkVersion = frameworkVersion
203
212
  const testSessionSpanContext = this.tracer.extract('text_map', {
@@ -213,8 +222,13 @@ class VitestPlugin extends CiPlugin {
213
222
  [TEST_SESSION_NAME]: testSessionName
214
223
  }
215
224
  }
216
- if (this.tracer._exporter.setMetadataTags) {
217
- this.tracer._exporter.setMetadataTags(metadataTags)
225
+ if (this.tracer._exporter.addMetadataTags) {
226
+ metadataTags.test = {
227
+ ...metadataTags.test,
228
+ [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionEnabled ? 'true' : 'false',
229
+ [DD_CAPABILITIES_AUTO_TEST_RETRIES]: isFlakyTestRetriesEnabled ? 'true' : 'false'
230
+ }
231
+ this.tracer._exporter.addMetadataTags(metadataTags)
218
232
  }
219
233
 
220
234
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
@@ -277,7 +291,7 @@ class VitestPlugin extends CiPlugin {
277
291
  testCodeCoverageLinesTotal,
278
292
  isEarlyFlakeDetectionEnabled,
279
293
  isEarlyFlakeDetectionFaulty,
280
- isQuarantinedTestsEnabled,
294
+ isTestManagementTestsEnabled,
281
295
  onFinish
282
296
  }) => {
283
297
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -296,7 +310,7 @@ class VitestPlugin extends CiPlugin {
296
310
  if (isEarlyFlakeDetectionFaulty) {
297
311
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ABORT_REASON, 'faulty')
298
312
  }
299
- if (isQuarantinedTestsEnabled) {
313
+ if (isTestManagementTestsEnabled) {
300
314
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
301
315
  }
302
316
  this.testModuleSpan.finish()
@@ -304,10 +318,20 @@ class VitestPlugin extends CiPlugin {
304
318
  this.testSessionSpan.finish()
305
319
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
306
320
  finishAllTraceSpans(this.testSessionSpan)
307
- this.telemetry.count(TELEMETRY_TEST_SESSION, { provider: this.ciProviderName })
321
+ this.telemetry.count(TELEMETRY_TEST_SESSION, {
322
+ provider: this.ciProviderName,
323
+ autoInjected: !!process.env.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER
324
+ })
308
325
  this.tracer._exporter.flush(onFinish)
309
326
  })
310
327
  }
328
+
329
+ getTestProperties (testManagementTests, testSuite, testName) {
330
+ const { disabled: isDisabled, quarantined: isQuarantined } =
331
+ testManagementTests?.vitest?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
332
+
333
+ return { isDisabled, isQuarantined }
334
+ }
311
335
  }
312
336
 
313
337
  module.exports = VitestPlugin
@@ -8,12 +8,16 @@ const { AUTO_REJECT, USER_REJECT } = require('../../../../ext/priority')
8
8
  const MAX_SIZE = 4096
9
9
 
10
10
  let enabled
11
+
12
+ /**
13
+ * @type {TTLCache}
14
+ */
11
15
  let sampledRequests
12
16
 
13
17
  class NoopTTLCache {
14
18
  clear () { }
15
- set (key) { return undefined }
16
- has (key) { return false }
19
+ set (_key, _value) { return undefined }
20
+ has (_key) { return false }
17
21
  }
18
22
 
19
23
  function configure ({ apiSecurity }) {
@@ -48,7 +52,7 @@ function sampleRequest (req, res, force = false) {
48
52
  }
49
53
 
50
54
  if (force) {
51
- sampledRequests.set(key)
55
+ sampledRequests.set(key, undefined)
52
56
  }
53
57
 
54
58
  return true
@@ -100,29 +100,36 @@ function getBlockingData (req, specificType, actionParameters) {
100
100
  }
101
101
 
102
102
  function block (req, res, rootSpan, abortController, actionParameters = defaultBlockingActionParameters) {
103
- if (res.headersSent) {
104
- log.warn('[ASM] Cannot send blocking response when headers have already been sent')
105
- return
106
- }
103
+ try {
104
+ if (res.headersSent) {
105
+ log.warn('[ASM] Cannot send blocking response when headers have already been sent')
107
106
 
108
- const { body, headers, statusCode } = getBlockingData(req, null, actionParameters)
107
+ throw new Error('Headers have already been sent')
108
+ }
109
109
 
110
- rootSpan.addTags({
111
- 'appsec.blocked': 'true'
112
- })
110
+ const { body, headers, statusCode } = getBlockingData(req, null, actionParameters)
113
111
 
114
- for (const headerName of res.getHeaderNames()) {
115
- res.removeHeader(headerName)
116
- }
112
+ for (const headerName of res.getHeaderNames()) {
113
+ res.removeHeader(headerName)
114
+ }
117
115
 
118
- res.writeHead(statusCode, headers)
116
+ res.writeHead(statusCode, headers)
119
117
 
120
- // this is needed to call the original end method, since express-session replaces it
121
- res.constructor.prototype.end.call(res, body)
118
+ // this is needed to call the original end method, since express-session replaces it
119
+ res.constructor.prototype.end.call(res, body)
122
120
 
123
- responseBlockedSet.add(res)
121
+ rootSpan.setTag('appsec.blocked', 'true')
124
122
 
125
- abortController?.abort()
123
+ responseBlockedSet.add(res)
124
+ abortController?.abort()
125
+
126
+ return true
127
+ } catch (err) {
128
+ rootSpan?.setTag('_dd.appsec.block.failed', 1)
129
+ log.error('[ASM] Blocking error', err)
130
+
131
+ return false
132
+ }
126
133
  }
127
134
 
128
135
  function getBlockingAction (actions) {
@@ -7,6 +7,7 @@ const {
7
7
  getBlockingData,
8
8
  getBlockingAction
9
9
  } = require('./blocking')
10
+ const log = require('../log')
10
11
  const waf = require('./waf')
11
12
  const addresses = require('./addresses')
12
13
  const web = require('../plugins/util/web')
@@ -94,14 +95,20 @@ function beforeWriteApolloGraphqlResponse ({ abortController, abortData }) {
94
95
  const rootSpan = web.root(req)
95
96
  if (!rootSpan) return
96
97
 
97
- const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction)
98
- abortData.statusCode = blockingData.statusCode
99
- abortData.headers = blockingData.headers
100
- abortData.message = blockingData.body
98
+ try {
99
+ const blockingData = getBlockingData(req, specificBlockingTypes.GRAPHQL, requestData.wafAction)
100
+ abortData.statusCode = blockingData.statusCode
101
+ abortData.headers = blockingData.headers
102
+ abortData.message = blockingData.body
101
103
 
102
- rootSpan.setTag('appsec.blocked', 'true')
104
+ rootSpan.setTag('appsec.blocked', 'true')
103
105
 
104
- abortController?.abort()
106
+ abortController?.abort()
107
+ } catch (err) {
108
+ rootSpan.setTag('_dd.appsec.block.failed', 1)
109
+
110
+ log.error('[ASM] Blocking error', err)
111
+ }
105
112
  }
106
113
 
107
114
  graphqlRequestData.delete(req)
@@ -49,7 +49,6 @@ function handleResult (actions, req, res, abortController, config) {
49
49
 
50
50
  const blockingAction = getBlockingAction(actions)
51
51
  if (blockingAction) {
52
- const rootSpan = web.root(req)
53
52
  // Should block only in express
54
53
  if (rootSpan?.context()._name === 'express.request') {
55
54
  const abortError = new DatadogRaspAbortError(req, res, blockingAction)