diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 182a99016f0..6eebf860aa4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -407,6 +407,8 @@ You can also run the tests for multiple plugins at once by separating them with PLUGINS="amqplib|bluebird" yarn test:plugins ``` +The necessary shell commands for the setup can also be executed at once by the `yarn env` script. + ### Other Unit Tests There are several types of unit tests, for various types of components. The diff --git a/index.d.ts b/index.d.ts index 822016d22f1..6446b799401 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2048,16 +2048,31 @@ declare namespace tracer { */ interface google_genai extends Integration {} - /** @hidden */ - interface ExecutionArgs { - schema: any, - document: any, - rootValue?: any, - contextValue?: any, - variableValues?: any, - operationName?: string, - fieldResolver?: any, - typeResolver?: any, + /** @hidden - the `graphql.ExecutionArgs` passed to the `execute` call */ + interface ExecutionArgs { + schema: any, + document: any, + rootValue?: any, + contextValue?: any, + variableValues?: any, + operationName?: string, + fieldResolver?: any, + typeResolver?: any, + } + + interface FieldContext { + /** The `graphql.GraphQLResolveInfo` for the resolver call */ + info: any; + /** The arguments passed to the resolver */ + args: any; + /** The error thrown by the resolver, if any */ + error: null | Error; + /** The result returned by the resolver, if any */ + result: unknown; + /** The field context from the resolver of the parent field (a level up on the path) */ + parentField: FieldContext | null; + /** The nesting depth of the field in the query */ + depth: number; } /** @@ -2135,9 +2150,10 @@ declare namespace tracer { * @default {} */ hooks?: { - execute?: (span?: Span, args?: ExecutionArgs, res?: any) => void; + execute?: (span?: Span, args?: ExecutionArgs, result?: any) => void; validate?: (span?: Span, document?: any, errors?: any) => void; parse?: (span?: Span, source?: any, document?: any) => void; + resolve?: (span?: Span, field: FieldContext) => void; } } diff --git a/packages/datadog-instrumentations/src/graphql.js b/packages/datadog-instrumentations/src/graphql.js index bf8053e3150..fede3264d21 100644 --- a/packages/datadog-instrumentations/src/graphql.js +++ b/packages/datadog-instrumentations/src/graphql.js @@ -25,7 +25,7 @@ const executeErrorCh = channel('apm:graphql:execute:error') // resolve channels const startResolveCh = channel('apm:graphql:resolve:start') const finishResolveCh = channel('apm:graphql:resolve:finish') -const updateFieldCh = channel('apm:graphql:resolve:updateField') +const finalizeResolveCh = channel('apm:graphql:resolve:finalize') const resolveErrorCh = channel('apm:graphql:resolve:error') // parse channels @@ -38,13 +38,6 @@ const validateStartCh = channel('apm:graphql:validate:start') const validateFinishCh = channel('apm:graphql:validate:finish') const validateErrorCh = channel('apm:graphql:validate:error') -class AbortError extends Error { - constructor (message) { - super(message) - this.name = 'AbortError' - } -} - const types = new Set(['query', 'mutation', 'subscription']) function getOperation (document, operationName) { @@ -156,9 +149,8 @@ function wrapExecute (execute) { } const args = normalizeArgs(arguments, defaultFieldResolver) - const schema = args.schema - const document = args.document - const source = documentSources.get(document) + const { schema, document } = args + const docSource = documentSources.get(document) const contextValue = args.contextValue const operation = getOperation(document, args.operationName) @@ -169,10 +161,10 @@ function wrapExecute (execute) { const ctx = { operation, args, - docSource: documentSources.get(document), - source, - fields: {}, - abortController: new AbortController(), + docSource, + fields: new WeakMap(), // fields keyed by their Path object + finalizations: [], // fields whose `.finalize()` method needs to be invoked before finishing execution + abortController: new AbortController(), // allow startExecuteCh/startResolveCh subscribers to block execution } return startExecuteCh.runStores(ctx, () => { @@ -183,8 +175,10 @@ function wrapExecute (execute) { contexts.set(contextValue, ctx) - return callInAsyncScope(exe, this, arguments, ctx.abortController, (err, res) => { - if (finishResolveCh.hasSubscribers) finishResolvers(ctx) + return callInAsyncScope(exe, this, arguments, ctx.abortController.signal, (err, res) => { + if (finalizeResolveCh.hasSubscribers && ctx.finalizations.length) { + finalizeResolvers(ctx.finalizations) + } const error = err || (res && res.errors && res.errors[0]) @@ -193,7 +187,7 @@ function wrapExecute (execute) { executeErrorCh.publish(ctx) } - ctx.res = res + ctx.result = res finishExecuteCh.publish(ctx) }) }) @@ -211,13 +205,23 @@ function wrapResolve (resolve) { if (!ctx) return resolve.apply(this, arguments) - const field = assertField(ctx, info, args) + const field = createField(ctx, info, args) + ctx.fields.set(info.path, field) + + startResolveCh.publish(field) - return callInAsyncScope(resolve, this, arguments, ctx.abortController, (err) => { - field.ctx.error = err - field.ctx.info = info - field.ctx.field = field - updateFieldCh.publish(field.ctx) + if (field.finalize) { + // register for `.finalize()` invocation before execution finishes + ctx.finalizations.push(field) + } + + return callInAsyncScope(resolve, this, arguments, ctx.abortController.signal, (err, res) => { + if (err) { + field.error = err + resolveErrorCh.publish(field) + } + field.result = res + finishResolveCh.publish(field) }) } @@ -226,16 +230,15 @@ function wrapResolve (resolve) { return resolveAsync } -function callInAsyncScope (fn, thisArg, args, abortController, cb) { - cb = cb || (() => {}) - - if (abortController?.signal.aborted) { +function callInAsyncScope (fn, thisArg, args, abortSignal, cb) { + if (abortSignal.aborted) { cb(null, null) - throw new AbortError('Aborted') + throw abortSignal.reason } + let result try { - const result = fn.apply(thisArg, args) + result = fn.apply(thisArg, args) if (result && typeof result.then === 'function') { return result.then( res => { @@ -248,44 +251,38 @@ function callInAsyncScope (fn, thisArg, args, abortController, cb) { } ) } - cb(null, result) - return result } catch (err) { cb(err) throw err - } + } // else + cb(null, result) + return result } -function pathToArray (path) { - const flattened = [] - let curr = path - while (curr) { - flattened.push(curr.key) - curr = curr.prev +function createField (rootCtx, info, args) { + const parentField = getParentField(rootCtx, info.path) + return { + rootCtx, + info, + args, + error: null, + result: null, + parentField, + depth: parentField ? parentField.depth + 1 : 1, + finalize: null, // sometimes populated by GraphQLResolvePlugin in `resolve:start` handler + finishTime: 0, // populated by GraphQLResolvePlugin in `resolve:updateField` handler + // currentStore, parentStore - sometimes populated by GraphQLResolvePlugin.startSpan in `resolve:start` handler } - return flattened.reverse() } -function assertField (rootCtx, info, args) { - const pathInfo = info && info.path - - const path = pathToArray(pathInfo) - - const pathString = path.join('.') - const fields = rootCtx.fields - - let field = fields[pathString] - - if (!field) { - const fieldCtx = { info, rootCtx, args } - startResolveCh.publish(fieldCtx) - field = fields[pathString] = { - error: null, - ctx: fieldCtx, - } +function getParentField (rootCtx, path) { + let curr = path.prev + // Skip segments in (nested) lists + // Could also be done by `while (!rootCtx.fields.has(curr))` + while (curr && typeof curr.key === 'number') { + curr = curr.prev } - - return field + return rootCtx.fields.get(curr) ?? null } function wrapFields (type) { @@ -320,16 +317,9 @@ function wrapFieldType (field) { wrapFields(unwrappedType) } -function finishResolvers ({ fields }) { - for (const key of Object.keys(fields).reverse()) { - const field = fields[key] - field.ctx.finishTime = field.finishTime - field.ctx.field = field - if (field.error) { - field.ctx.error = field.error - resolveErrorCh.publish(field.ctx) - } - finishResolveCh.publish(field.ctx) +function finalizeResolvers (contexts) { + for (const fieldCtx of contexts) { + finalizeResolveCh.publish(fieldCtx) } } diff --git a/packages/datadog-plugin-graphql/src/execute.js b/packages/datadog-plugin-graphql/src/execute.js index 90ecac1f2a6..76db32d5aad 100644 --- a/packages/datadog-plugin-graphql/src/execute.js +++ b/packages/datadog-plugin-graphql/src/execute.js @@ -37,17 +37,17 @@ class GraphQLExecutePlugin extends TracingPlugin { } finish (ctx) { - const { res, args } = ctx + const { result, args } = ctx const span = ctx?.currentStore?.span || this.activeSpan - this.config.hooks.execute(span, args, res) - if (res?.errors) { - for (const err of res.errors) { + this.config.hooks.execute(span, args, result) + if (result?.errors) { + for (const err of result.errors) { extractErrorIntoSpanEvent(this._tracerConfig, span, err) } } super.finish(ctx) - return ctx.parentStore + // return ctx.parentStore } } diff --git a/packages/datadog-plugin-graphql/src/index.js b/packages/datadog-plugin-graphql/src/index.js index 91e79a2f4b4..13acc7ce9d7 100644 --- a/packages/datadog-plugin-graphql/src/index.js +++ b/packages/datadog-plugin-graphql/src/index.js @@ -61,12 +61,14 @@ function getVariablesFilter (config) { const noop = () => {} +// FIXME: should not be necessary given `TracingPlugin.configure` already does this defaulting function getHooks ({ hooks }) { const execute = hooks?.execute ?? noop const parse = hooks?.parse ?? noop const validate = hooks?.validate ?? noop + const resolve = hooks?.resolve ?? noop - return { execute, parse, validate } + return { execute, parse, validate, resolve } } module.exports = GraphQLPlugin diff --git a/packages/datadog-plugin-graphql/src/resolve.js b/packages/datadog-plugin-graphql/src/resolve.js index 801867a892c..f36751b8527 100644 --- a/packages/datadog-plugin-graphql/src/resolve.js +++ b/packages/datadog-plugin-graphql/src/resolve.js @@ -3,126 +3,154 @@ const dc = require('dc-polyfill') const TracingPlugin = require('../../dd-trace/src/plugins/tracing') -const collapsedPathSym = Symbol('collapsedPaths') - class GraphQLResolvePlugin extends TracingPlugin { static id = 'graphql' static operation = 'resolve' - start (fieldCtx) { - const { info, rootCtx, args } = fieldCtx - - const path = getPath(info, this.config) - - // we need to get the parent span to the field if it exists for correct span parenting - // of nested fields - const parentField = getParentField(rootCtx, pathToArray(info && info.path)) - const childOf = parentField?.ctx?.currentStore?.span + start (field) { + const { info, rootCtx, args, parentField } = field + // FIXME - https://github.com/DataDog/dd-trace-js/issues/7468 + if (this.config.collapse) field.depth += getListLevel(info.path) + if (!this.shouldInstrument(field)) return - fieldCtx.parent = parentField - - if (!shouldInstrument(this.config, path)) return - const computedPathString = path.join('.') + let childOf, path, ctx if (this.config.collapse) { - if (rootCtx.fields[computedPathString]) return - - if (!rootCtx[collapsedPathSym]) { - rootCtx[collapsedPathSym] = {} - } else if (rootCtx[collapsedPathSym][computedPathString]) { + const parent = parentField?.shared + const sharedContexts = parentField + ? parent.collapsedChildren + : (rootCtx.toplevelCollapsed ??= {}) + ctx = sharedContexts[info.fieldName] + if (ctx) { + // Add reference to the shared context object, + // for the `finish` subscription to store the `.finishTime` on + // and for children resolvers to find `.collapsedChildren`. + field.shared = ctx + this.enter(ctx.currentStore.span) // TODO: test this! return } - - rootCtx[collapsedPathSym][computedPathString] = true + childOf = parent ? parent.currentStore?.span : undefined + path = collapsePath(pathToArray(info.path)) + // create shared context + ctx = sharedContexts[info.fieldName] = { + fieldName: info.fieldName, + path, + parent, + finishTime: 0, + collapsedChildren: {}, // slightly pointless on leaf fields? + // more to be added by `startSpan` + } + field.shared = ctx + // make `field.finalize()` be called before operation execution ends: + field.finalize = finalizeCollapsedField + } else { + childOf = parentField ? parentField.currentStore?.span : undefined + path = pathToArray(info.path) + ctx = field } - const document = rootCtx.source - const fieldNode = info.fieldNodes.find(fieldNode => fieldNode.kind === 'Field') - const loc = this.config.source && document && fieldNode && fieldNode.loc - const source = loc && document.slice(loc.start, loc.end) + const docSource = rootCtx.docSource + const fieldNode = info.fieldNodes.find(fieldNode => fieldNode.kind === 'Field') // FIXME: https://github.com/graphql/graphql-js/issues/605#issuecomment-266160864 + const loc = this.config.source && docSource && fieldNode && fieldNode.loc + const source = loc && docSource.slice(loc.start, loc.end) const span = this.startSpan('graphql.resolve', { service: this.config.service, resource: `${info.fieldName}:${info.returnType}`, - childOf, + childOf, // span used by parent field (if it exists) for correct span parenting of nested fields type: 'graphql', meta: { 'graphql.field.name': info.fieldName, - 'graphql.field.path': computedPathString, + 'graphql.field.path': path.join('.'), 'graphql.field.type': info.returnType.name, 'graphql.source': source, }, - }, fieldCtx) + }, ctx) if (fieldNode && this.config.variables && fieldNode.arguments) { const variables = this.config.variables(info.variableValues) - for (const arg of fieldNode.arguments) { - if (arg.value?.name && arg.value.kind === 'Variable' && variables[arg.value.name.value]) { - const name = arg.value.name.value - span.setTag(`graphql.variables.${name}`, variables[name]) + for (const { value: argValue } of fieldNode.arguments) { + if (argValue.kind === 'Variable') { + const varName = argValue.name.value + if (variables[varName] != null) { + span.setTag(`graphql.variables.${varName}`, variables[varName]) + } } } } if (this.resolverStartCh.hasSubscribers) { - this.resolverStartCh.publish({ ctx: rootCtx, resolverInfo: getResolverInfo(info, args) }) + this.resolverStartCh.publish({ + abortController: rootCtx.abortController, + resolverInfo: getResolverInfo(info, args), + }) } - return fieldCtx.currentStore + // return ctx.currentStore // seems unused? This is not `bindStart`! } - constructor (...args) { - super(...args) + finish (field) { + if (!this.shouldInstrument(field)) return - this.addTraceSub('updateField', (ctx) => { - const { field, info, error } = ctx + if (this.config.collapse) { + const fieldCtx = field.shared + const span = fieldCtx.currentStore?.span + if (field.error) { + fieldCtx.error ??= field.error + // TODO: use `extractErrorIntoSpanEvent` + } + fieldCtx.finishTime = span._getTime ? span._getTime() : 0 + this.config.hooks.resolve(span, field) + } else { + const span = field.currentStore?.span + this.config.hooks.resolve(span, field) + span?.finish() + } + } - const path = getPath(info, this.config) + constructor (...args) { + super(...args) - if (!shouldInstrument(this.config, path)) return + this.resolverStartCh = dc.channel('datadog:graphql:resolver:start') + this.shouldInstrument = _field => false - const span = ctx?.currentStore?.span || this.activeSpan - field.finishTime = span._getTime ? span._getTime() : 0 - field.error = field.error || error + this.addTraceSub('finalize', field => { + field.finalize() }) - - this.resolverStartCh = dc.channel('datadog:graphql:resolver:start') } configure (config) { // this will disable resolve subscribers if `config.depth` is set to 0 super.configure(config.depth === 0 ? false : config) - } - - finish (ctx) { - const { finishTime } = ctx - - const span = ctx?.currentStore?.span || this.activeSpan - span.finish(finishTime) - return ctx.parentStore + this.shouldInstrument = config.depth < 0 + ? _field => true + : field => config.depth >= field.depth } } // helpers -function shouldInstrument (config, path) { - let depth = 0 - for (const item of path) { - if (typeof item === 'string') { - depth += 1 - } +/** The method that is put as `.finalize` on a field when a shared context is created */ +function finalizeCollapsedField () { + const { shared } = this + const span = shared.currentStore?.span + if (shared.error) { // an error from any of the fields (that failed first) + span.setTag('error', shared.error) // like `TracingPlugin.prototype.error(shared)` } - - return config.depth < 0 || config.depth >= depth + span?.finish(shared.finishTime) } -function getPath (info, config) { - const responsePathAsArray = config.collapse - ? withCollapse(pathToArray) - : pathToArray - return responsePathAsArray(info && info.path) +/** Count `*` chars in front of last path segment for a collapsed path */ +function getListLevel (path) { + let lvl = 0 + let curr = path.prev + while (curr && typeof curr.key === 'number') { + lvl++ + curr = curr.prev + } + return lvl } function pathToArray (path) { @@ -135,11 +163,8 @@ function pathToArray (path) { return flattened.reverse() } -function withCollapse (responsePathAsArray) { - return function () { - return responsePathAsArray.apply(this, arguments) - .map(segment => typeof segment === 'number' ? '*' : segment) - } +function collapsePath (pathArray) { + return pathArray.map(segment => typeof segment === 'number' ? '*' : segment) } function getResolverInfo (info, args) { @@ -173,19 +198,4 @@ function getResolverInfo (info, args) { return resolverInfo } -function getParentField (parentCtx, path) { - for (let i = path.length - 1; i > 0; i--) { - const field = getField(parentCtx, path.slice(0, i)) - if (field) { - return field - } - } - - return null -} - -function getField (parentCtx, path) { - return parentCtx.fields[path.join('.')] -} - module.exports = GraphQLResolvePlugin diff --git a/packages/datadog-plugin-graphql/src/validate.js b/packages/datadog-plugin-graphql/src/validate.js index 789df6a9da8..09b73a9e75c 100644 --- a/packages/datadog-plugin-graphql/src/validate.js +++ b/packages/datadog-plugin-graphql/src/validate.js @@ -33,7 +33,7 @@ class GraphQLValidatePlugin extends TracingPlugin { } super.finish(ctx) - return ctx.parentStore + // return ctx.parentStore } } diff --git a/packages/dd-trace/src/appsec/channels.js b/packages/dd-trace/src/appsec/channels.js index 545a2dd7643..4ef1bb3492c 100644 --- a/packages/dd-trace/src/appsec/channels.js +++ b/packages/dd-trace/src/appsec/channels.js @@ -42,7 +42,7 @@ module.exports = { routerParam: dc.channel('datadog:router:param:start'), setCookieChannel: dc.channel('datadog:iast:set-cookie'), setUncaughtExceptionCaptureCallbackStart: dc.channel('datadog:process:setUncaughtExceptionCaptureCallback:start'), - startGraphqlResolve: dc.channel('datadog:graphql:resolver:start'), + startGraphqlResolver: dc.channel('datadog:graphql:resolver:start'), stripeCheckoutSessionCreate: dc.channel('datadog:stripe:checkoutSession:create:finish'), stripeConstructEvent: dc.channel('datadog:stripe:constructEvent:finish'), stripePaymentIntentCreate: dc.channel('datadog:stripe:paymentIntent:create:finish'), diff --git a/packages/dd-trace/src/appsec/graphql.js b/packages/dd-trace/src/appsec/graphql.js index 07dc437bff2..0419222dc79 100644 --- a/packages/dd-trace/src/appsec/graphql.js +++ b/packages/dd-trace/src/appsec/graphql.js @@ -12,7 +12,7 @@ const { const waf = require('./waf') const addresses = require('./addresses') const { - startGraphqlResolve, + startGraphqlResolver, graphqlMiddlewareChannel, apolloHttpServerChannel, apolloChannel, @@ -32,7 +32,7 @@ function disable () { disableGraphql() } -function onGraphqlStartResolve ({ context, resolverInfo }) { +function onGraphqlStartResolver ({ abortController, resolverInfo }) { const req = storage('legacy').getStore()?.req if (!req) return @@ -46,7 +46,7 @@ function onGraphqlStartResolve ({ context, resolverInfo }) { if (requestData?.isInGraphqlRequest) { requestData.blocked = true requestData.wafAction = blockingAction - context?.abortController?.abort() + abortController?.abort() } } } @@ -153,11 +153,11 @@ function disableApollo () { } function enableGraphql () { - startGraphqlResolve.subscribe(onGraphqlStartResolve) + startGraphqlResolver.subscribe(onGraphqlStartResolver) } function disableGraphql () { - if (startGraphqlResolve.hasSubscribers) startGraphqlResolve.unsubscribe(onGraphqlStartResolve) + if (startGraphqlResolver.hasSubscribers) startGraphqlResolver.unsubscribe(onGraphqlStartResolver) } module.exports = { diff --git a/packages/dd-trace/test/appsec/graphql.spec.js b/packages/dd-trace/test/appsec/graphql.spec.js index 89b0b31c919..0418c0af09f 100644 --- a/packages/dd-trace/test/appsec/graphql.spec.js +++ b/packages/dd-trace/test/appsec/graphql.spec.js @@ -11,7 +11,7 @@ const addresses = require('../../src/appsec/addresses') const waf = require('../../src/appsec/waf') const web = require('../../src/plugins/util/web') const { - startGraphqlResolve, + startGraphqlResolver, graphqlMiddlewareChannel, apolloChannel, apolloServerCoreChannel, @@ -63,7 +63,7 @@ describe('GraphQL', () => { assert.strictEqual(apolloChannel.asyncEnd.hasSubscribers, false) assert.strictEqual(apolloServerCoreChannel.start.hasSubscribers, false) assert.strictEqual(apolloServerCoreChannel.asyncEnd.hasSubscribers, false) - assert.strictEqual(startGraphqlResolve.hasSubscribers, false) + assert.strictEqual(startGraphqlResolver.hasSubscribers, false) graphql.enable() @@ -72,7 +72,7 @@ describe('GraphQL', () => { assert.strictEqual(apolloChannel.asyncEnd.hasSubscribers, true) assert.strictEqual(apolloServerCoreChannel.start.hasSubscribers, true) assert.strictEqual(apolloServerCoreChannel.asyncEnd.hasSubscribers, true) - assert.strictEqual(startGraphqlResolve.hasSubscribers, true) + assert.strictEqual(startGraphqlResolver.hasSubscribers, true) }) }) @@ -85,7 +85,7 @@ describe('GraphQL', () => { assert.strictEqual(apolloChannel.asyncEnd.hasSubscribers, true) assert.strictEqual(apolloServerCoreChannel.start.hasSubscribers, true) assert.strictEqual(apolloServerCoreChannel.asyncEnd.hasSubscribers, true) - assert.strictEqual(startGraphqlResolve.hasSubscribers, true) + assert.strictEqual(startGraphqlResolver.hasSubscribers, true) graphql.disable() @@ -94,11 +94,11 @@ describe('GraphQL', () => { assert.strictEqual(apolloChannel.asyncEnd.hasSubscribers, false) assert.strictEqual(apolloServerCoreChannel.start.hasSubscribers, false) assert.strictEqual(apolloServerCoreChannel.asyncEnd.hasSubscribers, false) - assert.strictEqual(startGraphqlResolve.hasSubscribers, false) + assert.strictEqual(startGraphqlResolver.hasSubscribers, false) }) }) - describe('onGraphqlStartResolve', () => { + describe('onGraphqlStartResolver', () => { beforeEach(() => { sinon.stub(waf, 'run').returns(['']) sinon.stub(storage('legacy'), 'getStore').returns({ req: {} }) @@ -116,7 +116,7 @@ describe('GraphQL', () => { resolver: undefined, } - startGraphqlResolve.publish({ context }) + startGraphqlResolver.publish(context) sinon.assert.notCalled(waf.run) }) @@ -126,32 +126,29 @@ describe('GraphQL', () => { resolver: '', } - startGraphqlResolve.publish({ context }) + startGraphqlResolver.publish(context) sinon.assert.notCalled(waf.run) }) it('Should not call waf if req is unavailable', () => { - const context = {} const resolverInfo = { user: [{ id: '1234' }], } storage('legacy').getStore().req = undefined - startGraphqlResolve.publish({ context, resolverInfo }) + startGraphqlResolver.publish({ resolverInfo }) sinon.assert.notCalled(waf.run) }) it('Should call waf if resolvers is well formatted', () => { - const context = {} - const resolverInfo = { user: [{ id: '1234' }], } - startGraphqlResolve.publish({ context, resolverInfo }) + startGraphqlResolver.publish({ resolverInfo }) sinon.assert.calledOnceWithExactly(waf.run, { ephemeral: { @@ -173,7 +170,7 @@ describe('GraphQL', () => { grpc_status_code: 10, } - let context, rootSpan + let abortController, rootSpan beforeEach(() => { sinon.stub(storage('legacy'), 'getStore').returns({ req, res }) @@ -181,10 +178,8 @@ describe('GraphQL', () => { graphql.enable() graphqlMiddlewareChannel.start.publish({ req, res }) apolloChannel.start.publish() - context = { - abortController: { - abort: sinon.stub(), - }, + abortController = { + abort: sinon.stub(), } rootSpan = { setTag: sinon.stub() } }) @@ -195,11 +190,9 @@ describe('GraphQL', () => { }) it('Should not call abort', () => { - const abortController = {} - sinon.stub(waf, 'run').returns(['']) - startGraphqlResolve.publish({ context, resolverInfo }) + startGraphqlResolver.publish({ abortController, resolverInfo }) sinon.assert.calledOnceWithExactly(waf.run, { ephemeral: { @@ -207,16 +200,14 @@ describe('GraphQL', () => { }, }, {}) - sinon.assert.notCalled(context.abortController.abort) + sinon.assert.notCalled(abortController.abort) - apolloChannel.asyncEnd.publish({ abortController }) + apolloChannel.asyncEnd.publish({ abortController: {} }) sinon.assert.notCalled(blocking.getBlockingData) }) it('Should call abort', () => { - const abortController = context.abortController - sinon.stub(waf, 'run').returns({ actions: { block_request: blockParameters, @@ -225,7 +216,7 @@ describe('GraphQL', () => { sinon.stub(web, 'root').returns(rootSpan) - startGraphqlResolve.publish({ context, resolverInfo }) + startGraphqlResolver.publish({ abortController, resolverInfo }) sinon.assert.calledOnceWithExactly(waf.run, { ephemeral: { @@ -233,7 +224,7 @@ describe('GraphQL', () => { }, }, {}) - sinon.assert.called(context.abortController.abort) + sinon.assert.called(abortController.abort) const abortData = {} apolloChannel.asyncEnd.publish({ abortController, abortData }) @@ -247,8 +238,6 @@ describe('GraphQL', () => { it('Should catch error when block fails', () => { blocking.getBlockingData.returns(undefined) - const abortController = context.abortController - sinon.stub(waf, 'run').returns({ actions: { block_request: blockParameters, @@ -257,7 +246,7 @@ describe('GraphQL', () => { sinon.stub(web, 'root').returns(rootSpan) - startGraphqlResolve.publish({ context, resolverInfo }) + startGraphqlResolver.publish({ abortController, resolverInfo }) sinon.assert.calledOnceWithExactly(waf.run, { ephemeral: { diff --git a/plugin-env b/plugin-env index 78166b8ca72..4d3b04390b5 100755 --- a/plugin-env +++ b/plugin-env @@ -13,7 +13,7 @@ if [ -z "$plugin_name" ]; then node - << EOF const fs=require('fs'); const yaml = require('yaml'); - const pluginsData = fs.readFileSync('.github/workflows/plugins.yml', 'utf8'); + const pluginsData = fs.readFileSync('.github/workflows/apm-integrations.yml', 'utf8'); const env=Object.keys(yaml.parse(pluginsData).jobs); console.log(...env); EOF @@ -36,7 +36,7 @@ fi read -r PLUGINS SERVICES <<<$(node - << EOF const fs=require('fs'); const yaml = require('yaml'); -const pluginsData = fs.readFileSync('.github/workflows/plugins.yml', 'utf8'); +const pluginsData = fs.readFileSync('.github/workflows/apm-integrations.yml', 'utf8'); const { PLUGINS, SERVICES } = yaml.parse(pluginsData).jobs['$plugin_name'].env; console.log(PLUGINS || '', SERVICES || '') EOF