11//! Tester-cookie endpoint helpers.
22//!
3- //! The tester route is intentionally disabled unless configured. When enabled,
4- //! it sets a first-party `ts-tester=true ` cookie scoped to the configured
3+ //! The tester routes are intentionally disabled unless configured. When enabled,
4+ //! they set or clear a first-party `ts-tester` cookie scoped to the configured
55//! publisher cookie domain.
66
77use edgezero_core:: body:: Body as EdgeBody ;
@@ -20,6 +20,14 @@ fn format_tester_cookie(domain: &str) -> String {
2020 )
2121}
2222
23+ /// Formats the tester cookie clearing `Set-Cookie` header value.
24+ fn format_clear_tester_cookie ( domain : & str ) -> String {
25+ format ! (
26+ "{}=; Domain={}; Path=/; Secure; SameSite=Lax; Max-Age=0" ,
27+ COOKIE_TS_TESTER , domain,
28+ )
29+ }
30+
2331/// Handles `GET /_ts/set-tester`.
2432///
2533/// Returns `404 Not Found` while `[tester_cookie].enabled` is false. When the
@@ -57,6 +65,44 @@ pub fn handle_set_tester(
5765 Ok ( response)
5866}
5967
68+ /// Handles `GET /_ts/clear-tester`.
69+ ///
70+ /// Returns `404 Not Found` while `[tester_cookie].enabled` is false. When the
71+ /// feature is enabled, returns `204 No Content` with `Set-Cookie` expiring the
72+ /// `ts-tester` cookie scoped to `publisher.cookie_domain`.
73+ ///
74+ /// # Errors
75+ ///
76+ /// Returns [`TrustedServerError::InvalidHeaderValue`] if the configured cookie
77+ /// domain cannot be rendered as an HTTP header value.
78+ pub fn handle_clear_tester (
79+ settings : & Settings ,
80+ ) -> Result < Response < EdgeBody > , Report < TrustedServerError > > {
81+ if !settings. tester_cookie . enabled {
82+ let mut response = Response :: new ( EdgeBody :: empty ( ) ) ;
83+ * response. status_mut ( ) = StatusCode :: NOT_FOUND ;
84+ return Ok ( response) ;
85+ }
86+
87+ let set_cookie = HeaderValue :: from_str ( & format_clear_tester_cookie (
88+ & settings. publisher . cookie_domain ,
89+ ) )
90+ . change_context ( TrustedServerError :: InvalidHeaderValue {
91+ message : "tester cookie clear contains invalid header value" . to_string ( ) ,
92+ } ) ?;
93+
94+ let mut response = Response :: new ( EdgeBody :: empty ( ) ) ;
95+ * response. status_mut ( ) = StatusCode :: NO_CONTENT ;
96+ response. headers_mut ( ) . insert (
97+ header:: CACHE_CONTROL ,
98+ HeaderValue :: from_static ( "no-store, private" ) ,
99+ ) ;
100+ response
101+ . headers_mut ( )
102+ . insert ( header:: SET_COOKIE , set_cookie) ;
103+ Ok ( response)
104+ }
105+
60106#[ cfg( test) ]
61107mod tests {
62108 use super :: * ;
@@ -111,4 +157,56 @@ mod tests {
111157 "disabled tester route should not set a cookie"
112158 ) ;
113159 }
160+
161+ #[ test]
162+ fn clear_tester_cookie_uses_configured_cookie_domain ( ) {
163+ let mut settings = create_test_settings ( ) ;
164+ settings. tester_cookie . enabled = true ;
165+ settings. publisher . cookie_domain = ".tester.example" . to_string ( ) ;
166+
167+ let response = handle_clear_tester ( & settings) . expect ( "should build clear tester response" ) ;
168+
169+ assert_eq ! (
170+ response. status( ) ,
171+ StatusCode :: NO_CONTENT ,
172+ "enabled clear tester route should return no content"
173+ ) ;
174+ assert_eq ! (
175+ response
176+ . headers( )
177+ . get( header:: CACHE_CONTROL )
178+ . and_then( |value| value. to_str( ) . ok( ) ) ,
179+ Some ( "no-store, private" ) ,
180+ "clear tester route should not be cacheable"
181+ ) ;
182+ let set_cookie = response
183+ . headers ( )
184+ . get ( header:: SET_COOKIE )
185+ . expect ( "should clear tester cookie" )
186+ . to_str ( )
187+ . expect ( "should render set-cookie as utf-8" ) ;
188+ assert_eq ! (
189+ set_cookie,
190+ "ts-tester=; Domain=.tester.example; Path=/; Secure; SameSite=Lax; Max-Age=0" ,
191+ "tester cookie clear should use publisher.cookie_domain"
192+ ) ;
193+ }
194+
195+ #[ test]
196+ fn clear_tester_cookie_route_is_disabled_by_default ( ) {
197+ let settings = create_test_settings ( ) ;
198+
199+ let response =
200+ handle_clear_tester ( & settings) . expect ( "should build disabled clear tester response" ) ;
201+
202+ assert_eq ! (
203+ response. status( ) ,
204+ StatusCode :: NOT_FOUND ,
205+ "disabled clear tester route should return not found"
206+ ) ;
207+ assert ! (
208+ response. headers( ) . get( header:: SET_COOKIE ) . is_none( ) ,
209+ "disabled clear tester route should not set a cookie"
210+ ) ;
211+ }
114212}
0 commit comments