@@ -8,6 +8,23 @@ use edgezero_core::app::Hooks as _;
88use edgezero_core:: http:: request_builder;
99use trusted_server_adapter_cloudflare:: app:: TrustedServerApp ;
1010
11+ /// Return the set of (METHOD, path) pairs explicitly registered on the router.
12+ fn registered_routes ( ) -> Vec < ( String , String ) > {
13+ TrustedServerApp :: routes ( )
14+ . routes ( )
15+ . into_iter ( )
16+ . map ( |r| ( r. method ( ) . to_string ( ) , r. path ( ) . to_string ( ) ) )
17+ . collect ( )
18+ }
19+
20+ fn assert_route_registered ( method : & str , path : & str ) {
21+ let routes = registered_routes ( ) ;
22+ assert ! (
23+ routes. iter( ) . any( |( m, p) | m == method && p == path) ,
24+ "{method} {path} must be explicitly registered; registered routes: {routes:?}"
25+ ) ;
26+ }
27+
1128#[ test]
1229fn routes_build_without_panic ( ) {
1330 // build_state() may fail (no real settings in CI) — startup_error_router
@@ -92,152 +109,29 @@ async fn tsjs_route_is_routed_not_5xx() {
92109 assert ! ( status < 500 , "tsjs route must not 5xx: got {status}" ) ;
93110}
94111
95- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
96- async fn verify_signature_is_routed ( ) {
97- let router = TrustedServerApp :: routes ( ) ;
98- let req = request_builder ( )
99- . method ( "POST" )
100- . uri ( "/verify-signature" )
101- . header ( "content-type" , "application/json" )
102- . body ( edgezero_core:: body:: Body :: from ( "{}" ) )
103- . expect ( "should build request" ) ;
104- let resp = router. oneshot ( req) . await ;
105- assert_ne ! (
106- resp. status( ) . as_u16( ) ,
107- 404 ,
108- "/verify-signature must be routed"
109- ) ;
110- }
111-
112- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
113- async fn admin_rotate_key_is_routed ( ) {
114- let router = TrustedServerApp :: routes ( ) ;
115- let req = request_builder ( )
116- . method ( "POST" )
117- . uri ( "/admin/keys/rotate" )
118- . header ( "content-type" , "application/json" )
119- . body ( edgezero_core:: body:: Body :: from ( "{}" ) )
120- . expect ( "should build request" ) ;
121- let resp = router. oneshot ( req) . await ;
122- assert_ne ! (
123- resp. status( ) . as_u16( ) ,
124- 404 ,
125- "/admin/keys/rotate must be routed"
126- ) ;
127- }
128-
129- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
130- async fn admin_deactivate_key_is_routed ( ) {
131- let router = TrustedServerApp :: routes ( ) ;
132- let req = request_builder ( )
133- . method ( "POST" )
134- . uri ( "/admin/keys/deactivate" )
135- . header ( "content-type" , "application/json" )
136- . body ( edgezero_core:: body:: Body :: from ( "{}" ) )
137- . expect ( "should build request" ) ;
138- let resp = router. oneshot ( req) . await ;
139- assert_ne ! (
140- resp. status( ) . as_u16( ) ,
141- 404 ,
142- "/admin/keys/deactivate must be routed"
143- ) ;
144- }
145-
146- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
147- async fn auction_is_routed ( ) {
148- let router = TrustedServerApp :: routes ( ) ;
149- let req = request_builder ( )
150- . method ( "POST" )
151- . uri ( "/auction" )
152- . header ( "content-type" , "application/json" )
153- . body ( edgezero_core:: body:: Body :: from ( r#"{"adUnits":[]}"# ) )
154- . expect ( "should build request" ) ;
155- let resp = router. oneshot ( req) . await ;
156- assert_ne ! ( resp. status( ) . as_u16( ) , 404 , "/auction must be routed" ) ;
157- }
158-
159- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
160- async fn first_party_proxy_is_routed ( ) {
161- let router = TrustedServerApp :: routes ( ) ;
162- let req = request_builder ( )
163- . method ( "GET" )
164- . uri ( "/first-party/proxy" )
165- . body ( edgezero_core:: body:: Body :: empty ( ) )
166- . expect ( "should build request" ) ;
167- let resp = router. oneshot ( req) . await ;
168- // Handlers require valid outbound proxy settings; they may return 4xx/5xx in CI.
169- // The assertion is routing only: the path must not fall through to the 404 not-found handler.
170- assert_ne ! (
171- resp. status( ) . as_u16( ) ,
172- 404 ,
173- "/first-party/proxy must be routed"
174- ) ;
175- }
176-
177- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
178- async fn first_party_click_is_routed ( ) {
179- let router = TrustedServerApp :: routes ( ) ;
180- let req = request_builder ( )
181- . method ( "GET" )
182- . uri ( "/first-party/click" )
183- . body ( edgezero_core:: body:: Body :: empty ( ) )
184- . expect ( "should build request" ) ;
185- let resp = router. oneshot ( req) . await ;
186- assert_ne ! (
187- resp. status( ) . as_u16( ) ,
188- 404 ,
189- "/first-party/click must be routed"
190- ) ;
191- }
192-
193- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
194- async fn first_party_sign_get_is_routed ( ) {
195- let router = TrustedServerApp :: routes ( ) ;
196- let req = request_builder ( )
197- . method ( "GET" )
198- . uri ( "/first-party/sign" )
199- . body ( edgezero_core:: body:: Body :: empty ( ) )
200- . expect ( "should build request" ) ;
201- let resp = router. oneshot ( req) . await ;
202- assert_ne ! (
203- resp. status( ) . as_u16( ) ,
204- 404 ,
205- "GET /first-party/sign must be routed"
206- ) ;
207- }
208-
209- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
210- async fn first_party_sign_post_is_routed ( ) {
211- let router = TrustedServerApp :: routes ( ) ;
212- let req = request_builder ( )
213- . method ( "POST" )
214- . uri ( "/first-party/sign" )
215- . header ( "content-type" , "application/json" )
216- . body ( edgezero_core:: body:: Body :: from ( "{}" ) )
217- . expect ( "should build request" ) ;
218- let resp = router. oneshot ( req) . await ;
219- assert_ne ! (
220- resp. status( ) . as_u16( ) ,
221- 404 ,
222- "POST /first-party/sign must be routed"
223- ) ;
224- }
112+ /// Verify that every expected explicit route is registered in the route table.
113+ ///
114+ /// Uses [`RouterService::routes()`] for introspection rather than checking
115+ /// response status codes — wildcards (`/{*rest}`) can return non-404 even when
116+ /// an explicit registration is missing, making status-based checks false positives.
117+ #[ test]
118+ fn all_explicit_routes_are_registered ( ) {
119+ let expected: & [ ( & str , & str ) ] = & [
120+ ( "GET" , "/.well-known/trusted-server.json" ) ,
121+ ( "POST" , "/verify-signature" ) ,
122+ ( "POST" , "/admin/keys/rotate" ) ,
123+ ( "POST" , "/admin/keys/deactivate" ) ,
124+ ( "POST" , "/auction" ) ,
125+ ( "GET" , "/first-party/proxy" ) ,
126+ ( "GET" , "/first-party/click" ) ,
127+ ( "GET" , "/first-party/sign" ) ,
128+ ( "POST" , "/first-party/sign" ) ,
129+ ( "POST" , "/first-party/proxy-rebuild" ) ,
130+ ] ;
225131
226- #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
227- async fn first_party_proxy_rebuild_is_routed ( ) {
228- let router = TrustedServerApp :: routes ( ) ;
229- let req = request_builder ( )
230- . method ( "POST" )
231- . uri ( "/first-party/proxy-rebuild" )
232- . header ( "content-type" , "application/json" )
233- . body ( edgezero_core:: body:: Body :: from ( "{}" ) )
234- . expect ( "should build request" ) ;
235- let resp = router. oneshot ( req) . await ;
236- assert_ne ! (
237- resp. status( ) . as_u16( ) ,
238- 404 ,
239- "/first-party/proxy-rebuild must be routed"
240- ) ;
132+ for ( method, path) in expected {
133+ assert_route_registered ( method, path) ;
134+ }
241135}
242136
243137// ---------------------------------------------------------------------------
0 commit comments