@@ -37,6 +37,22 @@ class TestEndpoint
3737 end
3838end
3939
40+ struct TestResponse
41+ include Azu ::Response
42+
43+ def render
44+ " Hello, World!"
45+ end
46+ end
47+
48+ class SimpleEndpoint
49+ include Azu ::Endpoint (Azu ::Request , TestResponse )
50+
51+ def call : TestResponse
52+ TestResponse .new
53+ end
54+ end
55+
4056describe Azu ::Router do
4157 describe " route registration" do
4258 it " adds GET endpoint" do
@@ -238,4 +254,218 @@ describe Azu::Router do
238254 router.should be_a(Azu ::Router )
239255 end
240256 end
257+
258+ describe " path building optimization" do
259+ it " caches commonly requested paths" do
260+ router = Azu ::Router .new
261+ endpoint = SimpleEndpoint .new
262+ router.get(" /hello" , endpoint)
263+
264+ # Create a mock context
265+ request = HTTP ::Request .new(" GET" , " /hello" )
266+ io = IO ::Memory .new
267+ response = HTTP ::Server ::Response .new(io)
268+ context = HTTP ::Server ::Context .new(request, response)
269+
270+ # First call should build and cache the path
271+ router.process(context)
272+ response.close
273+
274+ # Verify the path is cached by checking internal state
275+ # Since path_cache is private, we'll test behavior indirectly
276+ io.rewind
277+ first_response = io.gets_to_end
278+ first_response.should contain(" Hello, World!" )
279+
280+ # Second call should use cached path
281+ io2 = IO ::Memory .new
282+ response2 = HTTP ::Server ::Response .new(io2)
283+ context2 = HTTP ::Server ::Context .new(HTTP ::Request .new(" GET" , " /hello" ), response2 )
284+
285+ router.process(context2)
286+ response2 .close
287+ io2.rewind
288+ second_response = io2.gets_to_end
289+ second_response.should contain(" Hello, World!" )
290+ end
291+
292+ it " handles WebSocket upgrade paths correctly" do
293+ router = Azu ::Router .new
294+
295+ # Register a WebSocket route for testing
296+ router.ws(" /ws-test" , TestChannel )
297+
298+ # Mock WebSocket request
299+ request = HTTP ::Request .new(" GET" , " /ws-test" )
300+ request.headers[" Upgrade" ] = " websocket"
301+ request.headers[" Connection" ] = " Upgrade"
302+
303+ io = IO ::Memory .new
304+ response = HTTP ::Server ::Response .new(io)
305+ context = HTTP ::Server ::Context .new(request, response)
306+
307+ # Should handle WebSocket path building without errors
308+ result = router.process(context)
309+ # WebSocket routes may return nil or empty string depending on implementation
310+ (result.nil? || result == " " ).should be_true
311+ end
312+
313+ it " pre-computes method cache at initialization" do
314+ router = Azu ::Router .new
315+
316+ # Test that common HTTP methods work correctly
317+ endpoint = SimpleEndpoint .new
318+
319+ # Test various HTTP methods
320+ %w(GET POST PUT PATCH DELETE) .each do |method |
321+ router.add(" /test-#{ method.downcase } " , endpoint, Azu ::Method .parse(method.downcase))
322+
323+ request = HTTP ::Request .new(method, " /test-#{ method.downcase } " )
324+ io = IO ::Memory .new
325+ response = HTTP ::Server ::Response .new(io)
326+ context = HTTP ::Server ::Context .new(request, response)
327+
328+ result = router.process(context)
329+ result.should be_a(String )
330+ end
331+ end
332+
333+ it " handles path normalization correctly" do
334+ router = Azu ::Router .new
335+ endpoint = SimpleEndpoint .new
336+ router.get(" /test" , endpoint)
337+
338+ # Test path with trailing slash
339+ request = HTTP ::Request .new(" GET" , " /test/" )
340+ io = IO ::Memory .new
341+ response = HTTP ::Server ::Response .new(io)
342+ context = HTTP ::Server ::Context .new(request, response)
343+
344+ router.process(context)
345+ response.close
346+ io.rewind
347+ result = io.gets_to_end
348+ result.should contain(" Hello, World!" )
349+ end
350+
351+ it " clears path cache when requested" do
352+ router = Azu ::Router .new
353+ endpoint = SimpleEndpoint .new
354+ router.get(" /cached-test" , endpoint)
355+
356+ # Make a request to populate cache
357+ request = HTTP ::Request .new(" GET" , " /cached-test" )
358+ io = IO ::Memory .new
359+ response = HTTP ::Server ::Response .new(io)
360+ context = HTTP ::Server ::Context .new(request, response)
361+
362+ router.process(context)
363+
364+ # Clear the cache
365+ router.clear_path_cache
366+
367+ # Make another request - should still work
368+ io2 = IO ::Memory .new
369+ response2 = HTTP ::Server ::Response .new(io2)
370+ context2 = HTTP ::Server ::Context .new(HTTP ::Request .new(" GET" , " /cached-test" ), response2 )
371+
372+ router.process(context2)
373+ response2 .close
374+ io2.rewind
375+ result = io2.gets_to_end
376+ result.should contain(" Hello, World!" )
377+ end
378+
379+ it " handles LRU cache eviction properly" do
380+ # This test would need access to internal cache size to be fully effective
381+ # For now, we'll test that the router continues to work with many requests
382+ router = Azu ::Router .new
383+ endpoint = SimpleEndpoint .new
384+
385+ # Add many routes
386+ (1 ..1200).each do |i |
387+ router.get(" /test#{ i } " , endpoint)
388+ end
389+
390+ # Make requests to exceed cache size (default 1000)
391+ (1 ..1200).each do |i |
392+ request = HTTP ::Request .new(" GET" , " /test#{ i } " )
393+ io = IO ::Memory .new
394+ response = HTTP ::Server ::Response .new(io)
395+ context = HTTP ::Server ::Context .new(request, response)
396+
397+ result = router.process(context)
398+ result.should be_a(String )
399+ end
400+ end
401+ end
402+
403+ describe " original functionality" do
404+ it " handles GET requests" do
405+ router = Azu ::Router .new
406+ endpoint = SimpleEndpoint .new
407+ router.get(" /hello" , endpoint)
408+
409+ request = HTTP ::Request .new(" GET" , " /hello" )
410+ io = IO ::Memory .new
411+ response = HTTP ::Server ::Response .new(io)
412+ context = HTTP ::Server ::Context .new(request, response)
413+
414+ router.process(context)
415+ response.close
416+ io.rewind
417+ result = io.gets_to_end
418+ result.should contain(" Hello, World!" )
419+ end
420+
421+ it " handles POST requests" do
422+ router = Azu ::Router .new
423+ endpoint = SimpleEndpoint .new
424+ router.post(" /data" , endpoint)
425+
426+ request = HTTP ::Request .new(" POST" , " /data" )
427+ io = IO ::Memory .new
428+ response = HTTP ::Server ::Response .new(io)
429+ context = HTTP ::Server ::Context .new(request, response)
430+
431+ router.process(context)
432+ response.close
433+ io.rewind
434+ result = io.gets_to_end
435+ result.should contain(" Hello, World!" )
436+ end
437+
438+ it " handles method override" do
439+ router = Azu ::Router .new
440+ endpoint = SimpleEndpoint .new
441+ router.put(" /update" , endpoint)
442+
443+ request = HTTP ::Request .new(" POST" , " /update?_method=PUT" )
444+ io = IO ::Memory .new
445+ response = HTTP ::Server ::Response .new(io)
446+ context = HTTP ::Server ::Context .new(request, response)
447+
448+ router.process(context)
449+ response.close
450+ io.rewind
451+ result = io.gets_to_end
452+ result.should contain(" Hello, World!" )
453+ end
454+
455+ it " handles unknown routes gracefully" do
456+ router = Azu ::Router .new
457+
458+ request = HTTP ::Request .new(" GET" , " /unknown" )
459+ io = IO ::Memory .new
460+ response = HTTP ::Server ::Response .new(io)
461+ context = HTTP ::Server ::Context .new(request, response)
462+
463+ # Should not raise exception, returns nil for 404s
464+ result = router.process(context)
465+ result.should be_nil
466+
467+ # Check that the response status is set to 404
468+ response.status_code.should eq(404 )
469+ end
470+ end
241471end
0 commit comments