|
1 |
| -import { Runtime } from '@sentry/types'; |
| 1 | +import * as sentryCore from '@sentry/core'; |
| 2 | +import { Hub } from '@sentry/hub'; |
| 3 | +import * as sentryHub from '@sentry/hub'; |
| 4 | +import { SpanStatus, Transaction } from '@sentry/tracing'; |
| 5 | +import { Runtime, Transaction as TransactionType } from '@sentry/types'; |
| 6 | +import * as http from 'http'; |
| 7 | +import * as net from 'net'; |
2 | 8 |
|
3 | 9 | import { Event, Request, User } from '../src';
|
4 |
| -import { parseRequest } from '../src/handlers'; |
| 10 | +import { NodeClient } from '../src/client'; |
| 11 | +import { parseRequest, tracingHandler } from '../src/handlers'; |
5 | 12 |
|
6 | 13 | describe('parseRequest', () => {
|
7 | 14 | let mockReq: { [key: string]: any };
|
@@ -164,4 +171,133 @@ describe('parseRequest', () => {
|
164 | 171 | expect(parsedRequest.transaction).toEqual('routeHandler');
|
165 | 172 | });
|
166 | 173 | });
|
167 |
| -}); |
| 174 | +}); // end describe('parseRequest()') |
| 175 | + |
| 176 | +describe('tracingHandler', () => { |
| 177 | + const urlString = 'http://dogs.are.great:1231/yay/'; |
| 178 | + const queryString = '?furry=yes&funny=very'; |
| 179 | + const fragment = '#adoptnotbuy'; |
| 180 | + |
| 181 | + const sentryTracingMiddleware = tracingHandler(); |
| 182 | + |
| 183 | + let req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; |
| 184 | + |
| 185 | + function createNoOpSpy() { |
| 186 | + const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it |
| 187 | + return jest.spyOn(noop, 'noop') as any; |
| 188 | + } |
| 189 | + |
| 190 | + beforeEach(() => { |
| 191 | + req = new http.IncomingMessage(new net.Socket()); |
| 192 | + req.url = `${urlString}`; |
| 193 | + req.method = 'GET'; |
| 194 | + res = new http.ServerResponse(req); |
| 195 | + next = createNoOpSpy(); |
| 196 | + }); |
| 197 | + |
| 198 | + afterEach(() => { |
| 199 | + jest.restoreAllMocks(); |
| 200 | + }); |
| 201 | + |
| 202 | + it('creates a transaction when handling a request', () => { |
| 203 | + const startTransaction = jest.spyOn(sentryCore, 'startTransaction'); |
| 204 | + |
| 205 | + sentryTracingMiddleware(req, res, next); |
| 206 | + |
| 207 | + expect(startTransaction).toHaveBeenCalled(); |
| 208 | + }); |
| 209 | + |
| 210 | + it("pulls parent's data from tracing header on the request", () => { |
| 211 | + req.headers = { 'sentry-trace': '12312012123120121231201212312012-1121201211212012-0' }; |
| 212 | + |
| 213 | + sentryTracingMiddleware(req, res, next); |
| 214 | + |
| 215 | + const transaction = (res as any).__sentry_transaction; |
| 216 | + |
| 217 | + expect(transaction.traceId).toEqual('12312012123120121231201212312012'); |
| 218 | + expect(transaction.parentSpanId).toEqual('1121201211212012'); |
| 219 | + expect(transaction.sampled).toEqual(false); |
| 220 | + }); |
| 221 | + |
| 222 | + it('puts its transaction on the scope', () => { |
| 223 | + const hub = new Hub(new NodeClient({ tracesSampleRate: 1.0 })); |
| 224 | + // we need to mock both of these because the tracing handler relies on `@sentry/core` while the sampler relies on |
| 225 | + // `@sentry/hub`, and mocking breaks the link between the two |
| 226 | + jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); |
| 227 | + jest.spyOn(sentryHub, 'getCurrentHub').mockReturnValue(hub); |
| 228 | + |
| 229 | + sentryTracingMiddleware(req, res, next); |
| 230 | + |
| 231 | + const transaction = sentryCore |
| 232 | + .getCurrentHub() |
| 233 | + .getScope() |
| 234 | + ?.getTransaction(); |
| 235 | + |
| 236 | + expect(transaction).toBeDefined(); |
| 237 | + expect(transaction).toEqual(expect.objectContaining({ name: `GET ${urlString}`, op: 'http.server' })); |
| 238 | + }); |
| 239 | + |
| 240 | + it('puts its transaction on the response object', () => { |
| 241 | + sentryTracingMiddleware(req, res, next); |
| 242 | + |
| 243 | + const transaction = (res as any).__sentry_transaction; |
| 244 | + |
| 245 | + expect(transaction).toBeDefined(); |
| 246 | + expect(transaction).toEqual(expect.objectContaining({ name: `GET ${urlString}`, op: 'http.server' })); |
| 247 | + }); |
| 248 | + |
| 249 | + it('pulls status code from the response', async () => { |
| 250 | + const transaction = new Transaction({ name: 'mockTransaction' }); |
| 251 | + jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as TransactionType); |
| 252 | + const finishTransaction = jest.spyOn(transaction, 'finish'); |
| 253 | + |
| 254 | + sentryTracingMiddleware(req, res, next); |
| 255 | + res.statusCode = 200; |
| 256 | + res.emit('finish'); |
| 257 | + |
| 258 | + expect(finishTransaction).toHaveBeenCalled(); |
| 259 | + expect(transaction.status).toBe(SpanStatus.Ok); |
| 260 | + expect(transaction.tags).toEqual(expect.objectContaining({ 'http.status_code': '200' })); |
| 261 | + }); |
| 262 | + |
| 263 | + it('strips query string from request path', () => { |
| 264 | + req.url = `${urlString}${queryString}`; |
| 265 | + |
| 266 | + sentryTracingMiddleware(req, res, next); |
| 267 | + |
| 268 | + const transaction = (res as any).__sentry_transaction; |
| 269 | + |
| 270 | + expect(transaction?.name).toBe(`GET ${urlString}`); |
| 271 | + }); |
| 272 | + |
| 273 | + it('strips fragment from request path', () => { |
| 274 | + req.url = `${urlString}${fragment}`; |
| 275 | + |
| 276 | + sentryTracingMiddleware(req, res, next); |
| 277 | + |
| 278 | + const transaction = (res as any).__sentry_transaction; |
| 279 | + |
| 280 | + expect(transaction?.name).toBe(`GET ${urlString}`); |
| 281 | + }); |
| 282 | + |
| 283 | + it('strips query string and fragment from request path', () => { |
| 284 | + req.url = `${urlString}${queryString}${fragment}`; |
| 285 | + |
| 286 | + sentryTracingMiddleware(req, res, next); |
| 287 | + |
| 288 | + const transaction = (res as any).__sentry_transaction; |
| 289 | + |
| 290 | + expect(transaction?.name).toBe(`GET ${urlString}`); |
| 291 | + }); |
| 292 | + |
| 293 | + it('closes the transaction when request processing is done', () => { |
| 294 | + const transaction = new Transaction({ name: 'mockTransaction' }); |
| 295 | + jest.spyOn(sentryCore, 'startTransaction').mockReturnValue(transaction as TransactionType); |
| 296 | + const finishTransaction = jest.spyOn(transaction, 'finish'); |
| 297 | + |
| 298 | + sentryTracingMiddleware(req, res, next); |
| 299 | + res.emit('finish'); |
| 300 | + |
| 301 | + expect(finishTransaction).toHaveBeenCalled(); |
| 302 | + }); |
| 303 | +}); // end describe('tracingHandler') |
0 commit comments