UNPKG

autotel-cloudflare

Version:

The #1 OpenTelemetry package for Cloudflare Workers - complete bindings coverage, native CF OTel integration, advanced sampling

177 lines (149 loc) 6.22 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { instrumentHyperdrive } from './hyperdrive'; import { trace, SpanStatusCode, SpanKind } from '@opentelemetry/api'; describe('Hyperdrive Binding Instrumentation', () => { let mockTracer: any; let mockSpan: any; let getTracerSpy: any; beforeEach(() => { mockSpan = { spanContext: () => ({ traceId: 'test-trace-id', spanId: 'test-span-id', traceFlags: 1, }), setAttribute: vi.fn(), setAttributes: vi.fn(), setStatus: vi.fn(), recordException: vi.fn(), end: vi.fn(), isRecording: () => true, updateName: vi.fn(), addEvent: vi.fn(), }; mockTracer = { startActiveSpan: vi.fn((name, options, fn) => { return fn(mockSpan); }), }; getTracerSpy = vi.spyOn(trace, 'getTracer').mockReturnValue(mockTracer as any); }); afterEach(() => { getTracerSpy.mockRestore(); }); function createMockHyperdrive(overrides: Partial<Hyperdrive> = {}): Hyperdrive { return { connect: vi.fn(async () => ({} as Socket)), connectionString: 'postgresql://user:secret-password@db.example.com:5432/mydb', host: 'db.example.com', port: 5432, user: 'user', password: 'secret-password', database: 'mydb', ...overrides, } as unknown as Hyperdrive; } describe('connect()', () => { it('should create span with correct attributes', async () => { const mockHyperdrive = createMockHyperdrive(); const instrumented = instrumentHyperdrive(mockHyperdrive, 'my-db'); await instrumented.connect(); expect(mockTracer.startActiveSpan).toHaveBeenCalledTimes(1); const [spanName, options] = mockTracer.startActiveSpan.mock.calls[0]; expect(spanName).toBe('Hyperdrive my-db: connect'); expect(options.kind).toBe(SpanKind.CLIENT); expect(options.attributes['db.system']).toBe('cloudflare-hyperdrive'); expect(options.attributes['db.operation']).toBe('connect'); expect(options.attributes['server.address']).toBe('db.example.com'); expect(options.attributes['server.port']).toBe(5432); expect(options.attributes['db.user']).toBe('user'); }); it('should handle errors correctly', async () => { const connectError = new Error('Connection refused'); const mockHyperdrive = createMockHyperdrive({ connect: vi.fn(async () => { throw connectError; }) as any, }); const instrumented = instrumentHyperdrive(mockHyperdrive, 'my-db'); await expect(instrumented.connect()).rejects.toThrow('Connection refused'); expect(mockSpan.recordException).toHaveBeenCalledWith(connectError); expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.ERROR, message: 'Connection refused', }); expect(mockSpan.end).toHaveBeenCalled(); }); it('should set OK status and end span on success', async () => { const mockSocket = { readable: true } as unknown as Socket; const mockHyperdrive = createMockHyperdrive({ connect: vi.fn(async () => mockSocket) as any, }); const instrumented = instrumentHyperdrive(mockHyperdrive, 'my-db'); const result = await instrumented.connect(); expect(result).toBe(mockSocket); expect(mockSpan.setStatus).toHaveBeenCalledWith({ code: SpanStatusCode.OK }); expect(mockSpan.end).toHaveBeenCalled(); }); it('should use default binding name when none provided', async () => { const mockHyperdrive = createMockHyperdrive(); const instrumented = instrumentHyperdrive(mockHyperdrive); await instrumented.connect(); const [spanName] = mockTracer.startActiveSpan.mock.calls[0]; expect(spanName).toBe('Hyperdrive hyperdrive: connect'); }); }); describe('this-binding', () => { it('should invoke connect() with original object as this, not the proxy', async () => { let receivedThis: any; const mockHyperdrive = { connect: vi.fn(async function(this: any) { // eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias receivedThis = this; return {} as Socket; }), connectionString: 'postgresql://user:pass@host:5432/db', host: 'host', port: 5432, user: 'user', password: 'pass', database: 'db', } as unknown as Hyperdrive; const instrumented = instrumentHyperdrive(mockHyperdrive, 'test'); await instrumented.connect(); expect(receivedThis).toBe(mockHyperdrive); }); }); describe('non-instrumented properties', () => { it('should pass through non-instrumented properties', () => { const mockHyperdrive = createMockHyperdrive(); const instrumented = instrumentHyperdrive(mockHyperdrive, 'my-db'); expect(instrumented.connectionString).toBe('postgresql://user:secret-password@db.example.com:5432/mydb'); expect(instrumented.host).toBe('db.example.com'); expect(instrumented.port).toBe(5432); expect(instrumented.user).toBe('user'); expect(instrumented.database).toBe('mydb'); }); }); describe('security', () => { it('should never record password as an attribute', async () => { const mockHyperdrive = createMockHyperdrive({ password: 'super-secret-password', }); const instrumented = instrumentHyperdrive(mockHyperdrive, 'my-db'); await instrumented.connect(); const [, options] = mockTracer.startActiveSpan.mock.calls[0]; // Verify password is not in the attributes const attributeKeys = Object.keys(options.attributes); for (const key of attributeKeys) { expect(options.attributes[key]).not.toBe('super-secret-password'); } expect(options.attributes['db.password']).toBeUndefined(); expect(options.attributes['password']).toBeUndefined(); // Also verify setAttribute was not called with the password for (const call of mockSpan.setAttribute.mock.calls) { expect(call[1]).not.toBe('super-secret-password'); } }); }); });