@@ -5,6 +5,7 @@ import { Express } from "./Express";
5
5
import { FileSystem } from "../sinks/FileSystem" ;
6
6
import { HTTPServer } from "./HTTPServer" ;
7
7
import { createTestAgent } from "../helpers/createTestAgent" ;
8
+ import { fetch } from "../helpers/fetch" ;
8
9
9
10
// Before require("express")
10
11
const agent = createTestAgent ( {
@@ -124,6 +125,20 @@ function getApp(userMiddleware = true) {
124
125
// A middleware that is used as a route
125
126
app . use ( "/api/*path" , apiMiddleware ) ;
126
127
128
+ const newRouter = express . Router ( ) ;
129
+ newRouter . get ( "/nested-router" , ( req , res ) => {
130
+ res . send ( getContext ( ) ) ;
131
+ } ) ;
132
+
133
+ app . use ( newRouter ) ;
134
+
135
+ const nestedApp = express ( ) ;
136
+ nestedApp . get ( "/" , ( req , res ) => {
137
+ res . send ( getContext ( ) ) ;
138
+ } ) ;
139
+
140
+ app . use ( "/nested-app" , nestedApp ) ;
141
+
127
142
app . get ( "/" , ( req , res ) => {
128
143
const context = getContext ( ) ;
129
144
@@ -554,3 +569,91 @@ t.test("it preserves original function name in Layer object", async () => {
554
569
1
555
570
) ;
556
571
} ) ;
572
+
573
+ t . test ( "it supports nested router" , async ( ) => {
574
+ const response = await request ( getApp ( ) ) . get ( "/nested-router" ) ;
575
+
576
+ t . match ( response . body , {
577
+ method : "GET" ,
578
+ source : "express" ,
579
+ route : "/nested-router" ,
580
+ } ) ;
581
+ } ) ;
582
+
583
+ t . test ( "it supports nested app" , async ( t ) => {
584
+ const response = await request ( getApp ( ) ) . get ( "/nested-app" ) ;
585
+
586
+ t . match ( response . body , {
587
+ method : "GET" ,
588
+ source : "express" ,
589
+ route : "/nested-app" ,
590
+ } ) ;
591
+ } ) ;
592
+
593
+ // Express instrumentation results in routes with no stack, crashing Ghost
594
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2271
595
+ // https://github.com/open-telemetry/opentelemetry-js-contrib/pull/2294
596
+ t . test (
597
+ "it keeps handle properties even if router is patched before instrumentation does it" ,
598
+ async ( ) => {
599
+ const { createServer } = require ( "http" ) as typeof import ( "http" ) ;
600
+ const expressApp = express ( ) ;
601
+ const router = express . Router ( ) ;
602
+
603
+ let routerLayer : { name : string ; handle : { stack : any [ ] } } | undefined =
604
+ undefined ;
605
+
606
+ const CustomRouter : ( ...p : Parameters < typeof router > ) => void = (
607
+ req ,
608
+ res ,
609
+ next
610
+ ) => router ( req , res , next ) ;
611
+
612
+ router . use ( "/:slug" , ( req , res , next ) => {
613
+ // On express v4, the router is available as `app._router`
614
+ // On express v5, the router is available as `app.router`
615
+ // @ts -expect-error stack is private
616
+ const stack = req . app . router . stack as any [ ] ;
617
+ routerLayer = stack . find ( ( router ) => router . name === "CustomRouter" ) ;
618
+ return res . status ( 200 ) . send ( "bar" ) ;
619
+ } ) ;
620
+
621
+ // The patched router now has express router's own properties in its prototype so
622
+ // they are not accessible through `Object.keys(...)`
623
+ // https://github.com/TryGhost/Ghost/blob/fefb9ec395df8695d06442b6ecd3130dae374d94/ghost/core/core/frontend/web/site.js#L192
624
+ Object . setPrototypeOf ( CustomRouter , router ) ;
625
+ expressApp . use ( CustomRouter ) ;
626
+
627
+ // supertest acts weird with the custom router, so we need to create a server manually
628
+ const server = createServer ( expressApp ) ;
629
+ await new Promise < void > ( ( resolve ) => {
630
+ server . listen ( 0 , resolve ) ;
631
+ } ) ;
632
+
633
+ if ( ! server ) {
634
+ throw new Error ( "server not found" ) ;
635
+ }
636
+
637
+ const address = server . address ( ) ;
638
+
639
+ if ( typeof address === "string" ) {
640
+ throw new Error ( "address is a string" ) ;
641
+ }
642
+
643
+ const response = await fetch ( {
644
+ url : new URL ( `http://localhost:${ address ! . port } /foo` ) ,
645
+ } ) ;
646
+ t . same ( response . body , "bar" ) ;
647
+ server . close ( ) ;
648
+
649
+ if ( ! routerLayer ) {
650
+ throw new Error ( "router layer not found" ) ;
651
+ }
652
+
653
+ t . ok (
654
+ // @ts -expect-error handle is private
655
+ routerLayer . handle . stack . length === 1 ,
656
+ "router layer stack is accessible"
657
+ ) ;
658
+ }
659
+ ) ;
0 commit comments