UNPKG

connect-datadog-graphql

Version:
615 lines (536 loc) 20.2 kB
const connectDatadog = require("./index"); const mockStatsDImplementation = { increment: jest.fn(), decrement: jest.fn(), timing: jest.fn(), timer: jest.fn(), asyncTimer: jest.fn(), asyncDistTimer: jest.fn(), histogram: jest.fn(), distribution: jest.fn(), gauge: jest.fn(), set: jest.fn(), unique: jest.fn(), close: jest.fn(), event: jest.fn(), }; jest.mock("hot-shots", () => ({ StatsD: jest.fn().mockImplementation(() => mockStatsDImplementation), })); describe("connectDatadog", () => { let req; let res; let next; const chunk = "<p>Here is a chunk</p>"; const encoding = "text/html"; beforeEach(() => { req = { route: { path: "/some/path" }, path: "/another/path", method: "GET", baseUrl: "http://www.fakeurl.com", }; res = { status: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis(), end: jest.fn().mockReturnThis(), statusCode: 200, }; next = jest.fn(); }); afterEach(() => { mockStatsDImplementation.histogram.mockReset(); mockStatsDImplementation.increment.mockReset(); }); const asyncConnectDatadogAndCallMiddleware = (options, timeoutInterval = 0) => new Promise((resolve) => { const middleware = connectDatadog(options); middleware(req, res, next); timeoutInterval > 0 ? setTimeout(() => resolve(res.end(chunk, encoding)), timeoutInterval) : (res.end(chunk, encoding), resolve()); }); const expectConnectedToDatadog = (stat, statTags, exactMatch = true) => { const checkStatTags = exactMatch ? statTags : expect.arrayContaining(statTags); expect(mockStatsDImplementation.histogram).toHaveBeenCalledWith( `${stat}.response_time`, expect.anything(), 1, checkStatTags ); expect(mockStatsDImplementation.increment).toHaveBeenCalledWith( `${stat}.response_code.${res.statusCode}`, 1, checkStatTags ); expect(mockStatsDImplementation.increment).toHaveBeenCalledWith( `${stat}.response_code.all`, 1, checkStatTags ); expect(next).toHaveBeenCalled(); }; describe("middleware", () => { it("calls next", () => { const middleware = connectDatadog({}); middleware(req, res, next); expect(next).toHaveBeenCalled(); }); }); describe("when res.end is called", () => { it("calls the original res.end", async () => { // connectDatadog monkey-patches res.end, so we need to test that the original one is called const originalEnd = res.end; await asyncConnectDatadogAndCallMiddleware({}); expect(originalEnd).toHaveBeenCalled(); }); it("should update the histogram", async () => { await asyncConnectDatadogAndCallMiddleware({}); expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); }); }); describe("when options are passed to the closure", () => { describe("new dogstatsd object", () => { let dogstatsd; beforeEach(() => { dogstatsd = { histogram: jest.fn(), distribution: jest.fn() }; }); it("gets a value for the dogstatsd option", async () => { await asyncConnectDatadogAndCallMiddleware({ dogstatsd }); expect(dogstatsd.histogram).toHaveBeenCalled(); }); it("uses the default value for the dogstatsd option if not passed", async () => { await asyncConnectDatadogAndCallMiddleware({}); expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); expect(dogstatsd.histogram).not.toHaveBeenCalled(); }); }); describe("stat", () => { describe("when the stat option is passed", () => { it("should send a metric name with the passed value", async () => { const stat = "foo"; const statTags = expect.anything(); await asyncConnectDatadogAndCallMiddleware({ stat, response_code: true, }); expectConnectedToDatadog(stat, statTags); }); }); describe("when the stat option is not passed", () => { it("should send a metric name with the default value", async () => { const stat = "node.express.router"; const statTags = expect.anything(); await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); }); }); describe("tags", () => { describe("when the tags option is passed", () => { describe("when the tags option is a list of tags", () => { it("should append a list of tags to the metric tag", async () => { const stat = "node.express.router"; const tags = ["foo:bar", "baz:ball"]; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ].concat(tags); await asyncConnectDatadogAndCallMiddleware({ response_code: true, tags, }); expectConnectedToDatadog(stat, statTags, false); }); }); }); describe("when the tags option is not passed", () => { it("uses the default value for the tags option if not passed", async () => { const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); }); }); describe("path", () => { describe("when the path option is truthy", () => { it("should append the path to the metric tag", async () => { const path = true; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, `path:${req.path}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true, path, }); expectConnectedToDatadog(stat, statTags, false); }); }); describe("when the path option is falsy", () => { it("should NOT append the path to the metric tag", async () => { const path = false; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; // path option is explicitly set to false await asyncConnectDatadogAndCallMiddleware({ response_code: true, path, }); expectConnectedToDatadog(stat, statTags); // default path option is used await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); }); describe("when the path is not set in the request", () => { describe("when base_url is truthly", () => { it("uses the base URL as the route", async () => { delete req.route; const stat = "node.express.router"; const statTags = [ `route:${req.baseUrl}`, `response_code:${res.statusCode}`, ]; await asyncConnectDatadogAndCallMiddleware({ base_url: true, response_code: true, }); expectConnectedToDatadog(stat, statTags, false); expect(next).toHaveBeenCalled(); }); }); describe("when base_url is falsely", () => { it("route tag is not specified", async () => { delete req.route; const stat = "node.express.router"; const statTags = [`response_code:${res.statusCode}`]; await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags, false); expect(next).toHaveBeenCalled(); }); }); }); }); describe("base_url", () => { describe("when the base_url option is truthy", () => { it("should append the baseUrl to the beginning of the route in the metric tag", () => { const base_url = true; const stat = "node.express.router"; const statTags = [ `route:${req.baseUrl}${req.route.path}`, `response_code:${res.statusCode}`, ]; asyncConnectDatadogAndCallMiddleware({ response_code: true, base_url, }); expectConnectedToDatadog(stat, statTags); }); }); describe("when the base_url option is falsy", () => { it("should NOT append the baseUrl to the beginning of the route in the metric tag", () => { const base_url = false; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; // base_url option is explicitly set to false asyncConnectDatadogAndCallMiddleware({ response_code: true, base_url, }); expectConnectedToDatadog(stat, statTags); // default base_url option is used asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); }); }); describe("response_code", () => { describe("when the response_code option is truthy", () => { it("should send metrics for the response_code", async () => { const response_code = true; await asyncConnectDatadogAndCallMiddleware({ response_code }); expect(mockStatsDImplementation.increment).toHaveBeenCalledTimes(2); expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); }); }); describe("when the response_code option is falsy", () => { it("should NOT send metrics for the response_code", async () => { const response_code = false; // response_code option is explicitly set to false await asyncConnectDatadogAndCallMiddleware({ response_code }); expect(mockStatsDImplementation.increment).not.toHaveBeenCalled(); expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); // default response_code option is used await asyncConnectDatadogAndCallMiddleware({}); expect(mockStatsDImplementation.increment).not.toHaveBeenCalled(); expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); }); }); }); describe("delim", () => { describe("when the route contains a pipe character", () => { describe("when the delim option is passed", () => { it("should replace the pipe character", async () => { const delim = "x"; const origRoute = "/route|with|pipe"; const routeWithoutPipes = `/route${delim}with${delim}pipe`; req.route.path = origRoute; const stat = "node.express.router"; const statTags = [`route:${routeWithoutPipes}`]; await asyncConnectDatadogAndCallMiddleware({ delim }); expect(mockStatsDImplementation.histogram).toHaveBeenCalledWith( `${stat}.response_time`, expect.anything(), 1, statTags ); }); }); describe("when the delim option is NOT passed", () => { it("should use the default delim", async () => { const origRoute = "/route|with|pipe"; const routeWithoutPipes = "/route-with-pipe"; // The default delim should be '-' req.route.path = origRoute; const stat = "node.express.router"; const statTags = [`route:${routeWithoutPipes}`]; await asyncConnectDatadogAndCallMiddleware({}); expect(mockStatsDImplementation.histogram).toHaveBeenCalledWith( `${stat}.response_time`, expect.anything(), 1, statTags ); }); }); }); describe("when the route does NOT contain a pipe character", () => { it("should not change the route", async () => { const origRoute = "/route_with_pipe"; req.route.path = origRoute; const stat = "node.express.router"; const statTags = [`route:${origRoute}`]; await asyncConnectDatadogAndCallMiddleware({}); expect(mockStatsDImplementation.histogram).toHaveBeenCalledWith( `${stat}.response_time`, expect.anything(), 1, statTags ); }); }); }); describe("response_time", () => { const TIME_TO_WAIT = 50; it("should send a valid response_time metric greater than 0", async () => { await asyncConnectDatadogAndCallMiddleware({}, TIME_TO_WAIT); const expectedResponseTime = mockStatsDImplementation.histogram.mock.calls[0][1]; expect(mockStatsDImplementation.histogram).toHaveBeenCalled(); expect(expectedResponseTime).toBeGreaterThanOrEqual(TIME_TO_WAIT); }); }); describe("method", () => { describe("when the method option is truthy", () => { it("should append the req.method to the metric tags", async () => { const method = true; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, `method:${req.method.toLowerCase()}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true, method, }); expectConnectedToDatadog(stat, statTags, false); }); }); describe("when the method option is falsy", () => { it("should not append the req.method to the metric tags", async () => { const method = false; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; // method option is explicitly set to false await asyncConnectDatadogAndCallMiddleware({ response_code: true, method, }); expectConnectedToDatadog(stat, statTags, false); // no method option is passed await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags, false); }); }); }); describe("protocol", () => { describe("when the protocol option is truthy", () => { it("should append req.protocol to the metric tags if req.protocol is defined", async () => { const protocol = true; req.protocol = "fake protocol"; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, `protocol:${req.protocol}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true, protocol, }); expectConnectedToDatadog(stat, statTags, false); }); it("should still send metrics if req.protocol is NOT defined", async () => { const protocol = true; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true, protocol, }); expectConnectedToDatadog(stat, statTags); }); }); describe("when the protocol option is falsy", () => { it("should NOT append req.protocol to the metric tags if req.protocol is defined", async () => { const protocol = false; req.protocol = "fake protocol"; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; // protocol option is explicitly set to false await asyncConnectDatadogAndCallMiddleware({ response_code: true, protocol, }); expectConnectedToDatadog(stat, statTags); // no protocol option is set await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); it("should still send metrics if req.protocol is NOT defined", async () => { const protocol = false; const stat = "node.express.router"; const statTags = [ `route:${req.route.path}`, `response_code:${res.statusCode}`, ]; // protocol option is explicitly set to false await asyncConnectDatadogAndCallMiddleware({ response_code: true, protocol, }); expectConnectedToDatadog(stat, statTags); // no protocol option is set await asyncConnectDatadogAndCallMiddleware({ response_code: true }); expectConnectedToDatadog(stat, statTags); }); }); }); describe("graphql_paths", () => { describe("when the graphql_paths option is an array", () => { it("should include graphql_operation", async () => { const graphql_paths = ["/graphql"]; const stat = "node.express.router"; const statTags = [ `route:/graphql`, `graphql_operation:query`, `graphql_operation_name:myOperation`, `response_code:${res.statusCode}`, ]; req = { route: { path: "/graphql" }, path: "/graphql", method: "POST", baseUrl: "http://www.fakeurl.com", body: { query: ` query myOperation { nodes { id } } `, operationName: "sampleOperation", }, }; await asyncConnectDatadogAndCallMiddleware({ response_code: true, graphql_paths, }); expectConnectedToDatadog(stat, statTags); }); }); describe("when the graphql_paths option is falsy", () => { it("should not include graphql_operation", async () => { const stat = "node.express.router"; const statTags = [ `route:/graphql`, `response_code:${res.statusCode}`, ]; req = { route: { path: "/graphql" }, path: "/graphql", method: "POST", baseUrl: "http://www.fakeurl.com", body: { query: ` query myOperation { nodes { id } } `, operationName: "sampleOperation", }, }; await asyncConnectDatadogAndCallMiddleware({ response_code: true, }); expectConnectedToDatadog(stat, statTags); }); }); }); describe("extra_attributes", () => { describe("when extra_attributes is present", () => { it("should include the extra_attributes", async () => { const graphql_paths = ["/graphql"]; const stat = "node.express.router"; const statTags = [ `route:/some/path`, `extra_attribute:arbitrary_value`, `response_code:${res.statusCode}`, ]; await asyncConnectDatadogAndCallMiddleware({ response_code: true, extra_attributes: ["extra_attribute:arbitrary_value"], }); expectConnectedToDatadog(stat, statTags); }); }); }); }); });