77
88import static org .junit .jupiter .api .Assertions .assertEquals ;
99import static org .junit .jupiter .api .Assertions .assertFalse ;
10+ import static org .junit .jupiter .api .Assertions .assertNull ;
1011import static org .junit .jupiter .api .Assertions .assertThrows ;
12+ import static org .junit .jupiter .api .Assertions .assertTrue ;
13+ import static org .mockito .ArgumentMatchers .anyString ;
14+ import static org .mockito .Mockito .mockStatic ;
15+
16+ import java .io .UnsupportedEncodingException ;
17+ import java .net .URLDecoder ;
18+ import java .net .URLEncoder ;
19+ import java .time .Duration ;
20+ import java .util .Optional ;
1121
1222import org .junit .jupiter .api .Test ;
23+ import org .mockito .MockedStatic ;
1324
1425import com .google .common .collect .ImmutableMap ;
26+ import com .typesafe .config .Config ;
1527import com .typesafe .config .ConfigFactory ;
1628
1729public class CookieTest {
1830
1931 @ Test
2032 public void encodeDecode () {
2133 assertEquals ("" , Cookie .encode (ImmutableMap .of ()));
34+ assertEquals ("" , Cookie .encode (null ));
2235 assertEquals (ImmutableMap .of (), Cookie .decode ("" ));
36+ assertEquals (ImmutableMap .of (), Cookie .decode (null ));
2337
2438 assertEquals ("foo=bar" , Cookie .encode (ImmutableMap .of ("foo" , "bar" )));
2539 assertEquals (ImmutableMap .of (), Cookie .decode ("foo" ));
2640 assertEquals (ImmutableMap .of (), Cookie .decode ("foo=" ));
41+ assertEquals (ImmutableMap .of (), Cookie .decode ("=foo" )); // eq == 0
2742 assertEquals (ImmutableMap .of ("foo" , "b" ), Cookie .decode ("foo=b" ));
2843 assertEquals (ImmutableMap .of ("foo" , "bar" ), Cookie .decode ("foo=bar" ));
2944 assertEquals (ImmutableMap .of ("foo" , "bar" ), Cookie .decode ("foo=bar&" ));
@@ -35,6 +50,27 @@ public void encodeDecode() {
3550 assertEquals ("foo=bar&x=y" , Cookie .encode (ImmutableMap .of ("foo" , "bar" , "x" , "y" )));
3651 }
3752
53+ @ Test
54+ public void encodeDecodeExceptions () {
55+ // Tests the unreachable UnsupportedEncodingException catch blocks using Mockito
56+ try (MockedStatic <URLEncoder > encoder = mockStatic (URLEncoder .class );
57+ MockedStatic <URLDecoder > decoder = mockStatic (URLDecoder .class )) {
58+
59+ encoder
60+ .when (() -> URLEncoder .encode (anyString (), anyString ()))
61+ .thenThrow (new UnsupportedEncodingException ("mock error" ));
62+
63+ assertThrows (
64+ UnsupportedEncodingException .class , () -> Cookie .encode (ImmutableMap .of ("a" , "b" )));
65+
66+ decoder
67+ .when (() -> URLDecoder .decode (anyString (), anyString ()))
68+ .thenThrow (new UnsupportedEncodingException ("mock error" ));
69+
70+ assertThrows (UnsupportedEncodingException .class , () -> Cookie .decode ("a=b" ));
71+ }
72+ }
73+
3874 @ Test
3975 public void signUnsign () {
4076 assertEquals (
@@ -53,6 +89,188 @@ public void signUnsign() {
5389 "RcFzlzECN2Lv32Ie9jfSWVr13j6OjllJwDDZe4mVS4c|foo=bar&x=u iq" , "987654345!$009P" ));
5490 }
5591
92+ @ Test
93+ public void signUnsignEdgeCasesAndExceptions () {
94+ String secret = "987654345!$009P" ;
95+ assertNull (Cookie .unsign ("noseparator" , secret ));
96+ assertNull (Cookie .unsign ("|emptyvalue" , secret ));
97+ assertNull (Cookie .unsign ("invalidHash|foo=bar" , secret ));
98+
99+ // Force exception in sign block (NullPointerException wrapped in SneakyThrows)
100+ assertThrows (RuntimeException .class , () -> Cookie .sign (null , secret ));
101+ }
102+
103+ @ Test
104+ public void testConstructorsGettersSettersAndClone () {
105+ Cookie c = new Cookie ("sid" );
106+ assertEquals ("sid" , c .getName ());
107+ assertNull (c .getValue ());
108+
109+ c .setValue ("123" );
110+ assertEquals ("123" , c .getValue ());
111+
112+ assertNull (c .getDomain ());
113+ assertEquals ("def.com" , c .getDomain ("def.com" ));
114+ c .setDomain ("foo.com" );
115+ assertEquals ("foo.com" , c .getDomain ());
116+ assertEquals ("foo.com" , c .getDomain ("def.com" ));
117+
118+ assertNull (c .getPath ());
119+ assertEquals ("/def" , c .getPath ("/def" ));
120+ c .setPath ("/api" );
121+ assertEquals ("/api" , c .getPath ());
122+ assertEquals ("/api" , c .getPath ("/def" ));
123+
124+ assertFalse (c .isHttpOnly ());
125+ c .setHttpOnly (true );
126+ assertTrue (c .isHttpOnly ());
127+
128+ assertFalse (c .isSecure ());
129+ c .setSecure (true );
130+ assertTrue (c .isSecure ());
131+
132+ assertEquals (-1 , c .getMaxAge ());
133+ c .setMaxAge (Duration .ofSeconds (60 ));
134+ assertEquals (60 , c .getMaxAge ());
135+
136+ c .setMaxAge (-5 );
137+ assertEquals (-1 , c .getMaxAge ());
138+
139+ c .setSameSite (SameSite .STRICT );
140+ assertEquals (SameSite .STRICT , c .getSameSite ());
141+
142+ c .setName ("new-name" );
143+ assertEquals ("new-name" , c .getName ());
144+
145+ Cookie cloned = c .clone ();
146+ assertEquals (c .getName (), cloned .getName ());
147+ assertEquals (c .getValue (), cloned .getValue ());
148+ assertEquals (c .getDomain (), cloned .getDomain ());
149+ assertEquals (c .getPath (), cloned .getPath ());
150+ assertEquals (c .isHttpOnly (), cloned .isHttpOnly ());
151+ assertEquals (c .isSecure (), cloned .isSecure ());
152+ assertEquals (c .getMaxAge (), cloned .getMaxAge ());
153+ assertEquals (c .getSameSite (), cloned .getSameSite ());
154+ }
155+
156+ @ Test
157+ public void testToStringAndToCookieString () {
158+ Cookie c = new Cookie ("foo" );
159+ assertEquals ("foo=" , c .toString ());
160+ assertEquals ("foo=" , c .toCookieString ());
161+
162+ c .setValue ("bar" );
163+ assertEquals ("foo=bar" , c .toString ());
164+
165+ c .setPath ("/" );
166+ c .setDomain ("example.com" );
167+ c .setSameSite (SameSite .LAX );
168+ c .setSecure (true );
169+ c .setHttpOnly (true );
170+ c .setMaxAge (0 );
171+
172+ String cs = c .toCookieString ();
173+ assertTrue (cs .contains ("foo=bar" ));
174+ assertTrue (cs .contains ("Path=/" ));
175+ assertTrue (cs .contains ("Domain=example.com" ));
176+ assertTrue (cs .contains ("SameSite=Lax" ));
177+ assertTrue (cs .contains ("Secure" ));
178+ assertTrue (cs .contains ("HttpOnly" ));
179+ assertTrue (cs .contains ("Max-Age=0" ));
180+ assertTrue (cs .contains ("Expires=Thu, 01-Jan-1970 00:00:00 GMT" )); // Check exact 0 expiration
181+
182+ c .setMaxAge (3600 ); // maxAge > 0 branch
183+ cs = c .toCookieString ();
184+ assertTrue (cs .contains ("Max-Age=3600" ));
185+ assertTrue (cs .contains ("Expires=" ));
186+
187+ c .setMaxAge (-1 ); // maxAge < 0 branch
188+ cs = c .toCookieString ();
189+ assertFalse (cs .contains ("Max-Age" ));
190+ assertFalse (cs .contains ("Expires" ));
191+ }
192+
193+ @ Test
194+ public void testQuotesAndEscaping () {
195+ Cookie c = new Cookie ("foo" , "v a l u e" ); // Space needs quotes
196+ assertEquals ("foo=\" v a l u e\" " , c .toCookieString ());
197+
198+ c = new Cookie ("foo" , "val,ue" ); // Comma
199+ assertEquals ("foo=\" val,ue\" " , c .toCookieString ());
200+
201+ c = new Cookie ("foo" , "val;ue" ); // Semicolon
202+ assertEquals ("foo=\" val;ue\" " , c .toCookieString ());
203+
204+ c = new Cookie ("foo" , "val\\ ue" ); // Backslash -> gets escaped
205+ assertEquals ("foo=\" val\\ \\ ue\" " , c .toCookieString ());
206+
207+ c = new Cookie ("foo" , "val\" ue" ); // Double quote -> gets escaped
208+ assertEquals ("foo=\" val\\ \" ue\" " , c .toCookieString ());
209+
210+ c = new Cookie ("foo" , "val\t ue" ); // Tab
211+ assertEquals ("foo=\" val\t ue\" " , c .toCookieString ());
212+
213+ // Already quoted -> doesn't need quotes if properly enclosed
214+ c = new Cookie ("foo" , "\" already quoted\" " );
215+ assertEquals ("foo=\" already quoted\" " , c .toCookieString ());
216+
217+ // Already quoted but effectively empty body -> length condition edge case
218+ c = new Cookie ("foo" , "\" \" " );
219+ assertEquals ("foo=\" \" " , c .toCookieString ());
220+
221+ // Single quote char requires escaping and surrounding quotes
222+ c = new Cookie ("foo" , "\" " );
223+ assertEquals ("foo=\" \\ \" \" " , c .toCookieString ());
224+ }
225+
226+ @ Test
227+ public void testSessionCookieFactory () {
228+ Cookie session = Cookie .session ("my-sid" );
229+ assertEquals ("my-sid" , session .getName ());
230+ assertNull (session .getValue ());
231+ assertEquals (-1 , session .getMaxAge ());
232+ assertTrue (session .isHttpOnly ());
233+ assertEquals ("/" , session .getPath ());
234+ }
235+
236+ @ Test
237+ public void testCreateFromConfig () {
238+ Config config =
239+ ConfigFactory .parseMap (
240+ ImmutableMap .<String , Object >builder ()
241+ .put ("session.name" , "sid" )
242+ .put ("session.value" , "123" )
243+ .put ("session.path" , "/app" )
244+ .put ("session.domain" , "localhost" )
245+ .put ("session.secure" , false )
246+ .put ("session.httpOnly" , true )
247+ .put ("session.maxAge" , "1h" )
248+ .put ("session.sameSite" , "Strict" )
249+ .build ());
250+
251+ Optional <Cookie > cOpt = Cookie .create ("session" , config );
252+ assertTrue (cOpt .isPresent ());
253+
254+ Cookie c = cOpt .get ();
255+ assertEquals ("sid" , c .getName ());
256+ assertEquals ("123" , c .getValue ());
257+ assertEquals ("/app" , c .getPath ());
258+ assertEquals ("localhost" , c .getDomain ());
259+ assertFalse (c .isSecure ());
260+ assertTrue (c .isHttpOnly ());
261+ assertEquals (3600 , c .getMaxAge ());
262+ assertEquals (SameSite .STRICT , c .getSameSite ());
263+
264+ // Empty config namespace branch
265+ assertFalse (Cookie .create ("missing" , config ).isPresent ());
266+
267+ // Partial config namespace branch (missing optional values)
268+ Config partialConfig = ConfigFactory .parseMap (ImmutableMap .of ("session.name" , "partial" ));
269+ Cookie partialCookie = Cookie .create ("session" , partialConfig ).get ();
270+ assertEquals ("partial" , partialCookie .getName ());
271+ assertNull (partialCookie .getValue ());
272+ }
273+
56274 @ Test
57275 public void testCreateSameSite () {
58276 assertEquals (
@@ -140,9 +358,13 @@ public void testSameSiteVsSecure() {
140358 + " allowing non-secure cookies before calling Cookie.setSecure(false)." ,
141359 t2 .getMessage ());
142360
143- cookie .setSameSite (null );
361+ // Allows secure = false if SameSite does not mandate it
362+ cookie .setSameSite (SameSite .LAX );
144363 cookie .setSecure (false );
364+ assertFalse (cookie .isSecure ());
145365
366+ cookie .setSameSite (null );
367+ cookie .setSecure (false );
146368 assertFalse (cookie .isSecure ());
147369 }
148370}
0 commit comments