11import { expect , test } from '@playwright/test' ;
22import { waitForStreamedSpan , getSpanOp } from '@sentry-internal/test-utils' ;
3- import { APP_NAME , RUNTIME } from './constants' ;
3+ import { APP_NAME , RUNTIME , type Runtime } from './constants' ;
4+
5+ const anyString = expect . any ( String ) as unknown ;
6+ const anyNumber = expect . any ( Number ) as unknown ;
7+ const ipvType = expect . stringMatching ( / ^ i p v [ 4 6 ] $ / ) as unknown ;
8+
9+ // Per-runtime `http.server` connection-info expectations. Each runtime's `getConnInfo` helper exposes
10+ // a different set of fields, so the expected attribute values are declared here once (keyed by
11+ // runtime) instead of branching inside the tests. `undefined` means the attribute must be absent.
12+ // Relational checks (`network.peer.*` mirroring `client.*`) are asserted in the tests, since they
13+ // hold on every runtime (including when both sides are absent).
14+ //
15+ // - `added`: attributes Hono's conninfo middleware contributes.
16+ // - `baseline`: attributes the SDK sends independent of conninfo (regression guard that conninfo only
17+ // adds, never clobbers).
18+ const CONN_INFO : Record < Runtime , { added : Record < string , unknown > ; baseline : Record < string , unknown > } > = {
19+ node : {
20+ added : { 'client.port' : anyNumber , 'network.type' : ipvType , 'network.transport' : undefined } ,
21+ baseline : {
22+ 'server.address' : 'localhost' ,
23+ 'server.port' : anyNumber ,
24+ 'client.address' : anyString ,
25+ 'client.port' : anyNumber ,
26+ 'network.type' : ipvType ,
27+ 'network.protocol.name' : 'http' ,
28+ 'network.protocol.version' : '1.1' ,
29+ 'network.transport' : undefined ,
30+ 'network.local.address' : anyString ,
31+ 'network.local.port' : anyNumber ,
32+ } ,
33+ } ,
34+ bun : {
35+ added : { 'client.port' : anyNumber , 'network.type' : ipvType , 'network.transport' : undefined } ,
36+ baseline : { 'client.address' : anyString , 'client.port' : anyNumber , 'network.type' : ipvType } ,
37+ } ,
38+ deno : {
39+ // Only `hono/deno` exposes `network.transport`.
40+ added : { 'client.port' : anyNumber , 'network.transport' : expect . stringMatching ( / t c p / ) } ,
41+ baseline : {
42+ 'server.address' : 'localhost' ,
43+ 'client.address' : anyString ,
44+ 'client.port' : anyNumber ,
45+ 'network.transport' : 'tcp' ,
46+ 'network.protocol.name' : 'http' ,
47+ } ,
48+ } ,
49+ cloudflare : {
50+ // Cloudflare Workers expose no port, address family, or transport. Asserting their absence lets
51+ // us notice if that ever changes.
52+ added : { 'client.port' : undefined , 'network.type' : undefined , 'network.transport' : undefined } ,
53+ baseline : {
54+ 'server.address' : 'localhost' ,
55+ 'client.address' : '::1' ,
56+ 'network.protocol.name' : 'http' ,
57+ 'network.protocol.version' : '1.1' ,
58+ } ,
59+ } ,
60+ } ;
61+
62+ const connInfo = CONN_INFO [ RUNTIME ] ;
463
564test ( 'sends a span for the index route' , async ( { baseURL } ) => {
665 const segmentPromise = waitForStreamedSpan (
@@ -46,31 +105,14 @@ test('attaches HTTP connection info to the server span', async ({ baseURL, page
46105 const data = segment . attributes ?? { } ;
47106
48107 expect ( data [ 'client.address' ] ?. value ) . toEqual ( expect . any ( String ) ) ;
49- expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
50108
51- if ( RUNTIME !== 'deno' ) {
52- // Only exposed in `hono/deno`
53- expect ( data [ 'network.transport' ] ?. value ) . toBeUndefined ( ) ;
54- } else {
55- expect ( data [ 'network.transport' ] ?. value ) . toMatch ( / t c p / ) ;
56- }
109+ // conninfo must only *add* attributes, never replace: peer mirrors client on every runtime
110+ // (including when both are absent).
111+ expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
112+ expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
57113
58- if ( RUNTIME === 'node' || RUNTIME === 'bun' ) {
59- // Node (@hono/node-server) and Bun expose socket-level port and address family.
60- expect ( data [ 'client.port' ] ?. value ) . toEqual ( expect . any ( Number ) ) ;
61- expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
62- expect ( data [ 'network.type' ] ?. value ) . toMatch ( / ^ i p v [ 4 6 ] $ / ) ;
63- } else if ( RUNTIME === 'deno' ) {
64- expect ( data [ 'client.port' ] ?. value ) . toEqual ( expect . any ( Number ) ) ;
65- expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
66- } else if ( RUNTIME === 'cloudflare' ) {
67- // Cloudflare Workers expose no port, address family, or transport.
68- // This could change in the future and checking for the absence of these fields allows us to notice if/when that happens.
69- expect ( data [ 'client.port' ] ?. value ) . toBeUndefined ( ) ;
70- expect ( data [ 'network.peer.port' ] ?. value ) . toBeUndefined ( ) ;
71- expect ( data [ 'network.type' ] ?. value ) . toBeUndefined ( ) ;
72- } else {
73- throw new Error ( `No tests for runtime: ${ RUNTIME } ` ) ;
114+ for ( const [ key , expected ] of Object . entries ( connInfo . added ) ) {
115+ expect ( data [ key ] ?. value ) . toEqual ( expected ) ;
74116 }
75117} ) ;
76118
@@ -91,42 +133,13 @@ test("preserves the baseline server.*, client.* and network.* server span attrib
91133 const segment = await segmentPromise ;
92134 const data = segment . attributes ?? { } ;
93135
94- if ( RUNTIME === 'node' ) {
95- expect ( data [ 'server.address' ] ?. value ) . toBe ( 'localhost' ) ;
96- expect ( data [ 'server.port' ] ?. value ) . toBe ( Number ( new URL ( baseURL ! ) . port ) ) ;
97- expect ( data [ 'client.address' ] ?. value ) . toEqual ( expect . any ( String ) ) ;
98- expect ( data [ 'client.port' ] ?. value ) . toEqual ( expect . any ( Number ) ) ;
99- expect ( data [ 'network.type' ] ?. value ) . toMatch ( / ^ i p v [ 4 6 ] $ / ) ;
100- expect ( data [ 'network.protocol.name' ] ?. value ) . toBe ( 'http' ) ;
101- expect ( data [ 'network.protocol.version' ] ?. value ) . toBe ( '1.1' ) ;
102- expect ( data [ 'network.transport' ] ?. value ) . toBeUndefined ( ) ;
103- expect ( data [ 'network.local.port' ] ?. value ) . toBe ( data [ 'server.port' ] ?. value ) ;
104- expect ( data [ 'network.local.address' ] ?. value ) . toEqual ( expect . any ( String ) ) ;
105- expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
106- expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
107- } else if ( RUNTIME === 'bun' ) {
108- expect ( data [ 'client.address' ] ?. value ) . toEqual ( expect . any ( String ) ) ;
109- expect ( data [ 'client.port' ] ?. value ) . toEqual ( expect . any ( Number ) ) ;
110- expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
111- expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
112- expect ( data [ 'network.type' ] ?. value ) . toMatch ( / ^ i p v [ 4 6 ] $ / ) ;
113- } else if ( RUNTIME === 'cloudflare' ) {
114- expect ( data [ 'server.address' ] ?. value ) . toBe ( 'localhost' ) ;
115- expect ( data [ 'client.address' ] ?. value ) . toBe ( '::1' ) ;
116- expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
117- expect ( data [ 'network.protocol.name' ] ?. value ) . toBe ( 'http' ) ;
118- expect ( data [ 'network.protocol.version' ] ?. value ) . toBe ( '1.1' ) ;
119- } else if ( RUNTIME === 'deno' ) {
120- expect ( data [ 'server.address' ] ?. value ) . toBe ( 'localhost' ) ;
121- expect ( data [ 'client.address' ] ?. value ) . toEqual ( expect . any ( String ) ) ;
122- expect ( data [ 'client.port' ] ?. value ) . toEqual ( expect . any ( Number ) ) ;
123- expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
124- expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
125- expect ( data [ 'network.transport' ] ?. value ) . toBe ( 'tcp' ) ;
126- expect ( data [ 'network.protocol.name' ] ?. value ) . toBe ( 'http' ) ;
127- } else {
128- throw new Error ( `No tests for runtime: ${ RUNTIME } ` ) ;
136+ for ( const [ key , expected ] of Object . entries ( connInfo . baseline ) ) {
137+ expect ( data [ key ] ?. value ) . toEqual ( expected ) ;
129138 }
139+
140+ // Relational checks that hold on every runtime (both sides absent → still equal).
141+ expect ( data [ 'network.peer.address' ] ?. value ) . toBe ( data [ 'client.address' ] ?. value ) ;
142+ expect ( data [ 'network.peer.port' ] ?. value ) . toBe ( data [ 'client.port' ] ?. value ) ;
130143} ) ;
131144
132145test ( 'sends a span for a route that throws' , async ( { baseURL } ) => {
0 commit comments