test_router.rb 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. # encoding: UTF-8
  2. require 'helper'
  3. module Journey
  4. class TestRouter < MiniTest::Unit::TestCase
  5. attr_reader :routes
  6. def setup
  7. @routes = Routes.new
  8. @router = Router.new(@routes, {})
  9. @formatter = Formatter.new(@routes)
  10. end
  11. def test_request_class_reader
  12. klass = Object.new
  13. router = Router.new(routes, :request_class => klass)
  14. assert_equal klass, router.request_class
  15. end
  16. class FakeRequestFeeler < Struct.new(:env, :called)
  17. def new env
  18. self.env = env
  19. self
  20. end
  21. def hello
  22. self.called = true
  23. 'world'
  24. end
  25. def path_info; env['PATH_INFO']; end
  26. def request_method; env['REQUEST_METHOD']; end
  27. def ip; env['REMOTE_ADDR']; end
  28. end
  29. def test_dashes
  30. klass = FakeRequestFeeler.new nil
  31. router = Router.new(routes, {})
  32. exp = Router::Strexp.new '/foo-bar-baz', {}, ['/.?']
  33. path = Path::Pattern.new exp
  34. routes.add_route nil, path, {}, {:id => nil}, {}
  35. env = rails_env 'PATH_INFO' => '/foo-bar-baz'
  36. called = false
  37. router.recognize(env) do |r, _, params|
  38. called = true
  39. end
  40. assert called
  41. end
  42. def test_unicode
  43. klass = FakeRequestFeeler.new nil
  44. router = Router.new(routes, {})
  45. #match the escaped version of /ほげ
  46. exp = Router::Strexp.new '/%E3%81%BB%E3%81%92', {}, ['/.?']
  47. path = Path::Pattern.new exp
  48. routes.add_route nil, path, {}, {:id => nil}, {}
  49. env = rails_env 'PATH_INFO' => '/%E3%81%BB%E3%81%92'
  50. called = false
  51. router.recognize(env) do |r, _, params|
  52. called = true
  53. end
  54. assert called
  55. end
  56. def test_request_class_and_requirements_success
  57. klass = FakeRequestFeeler.new nil
  58. router = Router.new(routes, {:request_class => klass })
  59. requirements = { :hello => /world/ }
  60. exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
  61. path = Path::Pattern.new exp
  62. routes.add_route nil, path, requirements, {:id => nil}, {}
  63. env = rails_env 'PATH_INFO' => '/foo/10'
  64. router.recognize(env) do |r, _, params|
  65. assert_equal({:id => '10'}, params)
  66. end
  67. assert klass.called, 'hello should have been called'
  68. assert_equal env.env, klass.env
  69. end
  70. def test_request_class_and_requirements_fail
  71. klass = FakeRequestFeeler.new nil
  72. router = Router.new(routes, {:request_class => klass })
  73. requirements = { :hello => /mom/ }
  74. exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?']
  75. path = Path::Pattern.new exp
  76. router.routes.add_route nil, path, requirements, {:id => nil}, {}
  77. env = rails_env 'PATH_INFO' => '/foo/10'
  78. router.recognize(env) do |r, _, params|
  79. flunk 'route should not be found'
  80. end
  81. assert klass.called, 'hello should have been called'
  82. assert_equal env.env, klass.env
  83. end
  84. class CustomPathRequest < Router::NullReq
  85. def path_info
  86. env['custom.path_info']
  87. end
  88. end
  89. def test_request_class_overrides_path_info
  90. router = Router.new(routes, {:request_class => CustomPathRequest })
  91. exp = Router::Strexp.new '/bar', {}, ['/.?']
  92. path = Path::Pattern.new exp
  93. routes.add_route nil, path, {}, {}, {}
  94. env = rails_env 'PATH_INFO' => '/foo', 'custom.path_info' => '/bar'
  95. recognized = false
  96. router.recognize(env) do |r, _, params|
  97. recognized = true
  98. end
  99. assert recognized, "route should have been recognized"
  100. end
  101. def test_regexp_first_precedence
  102. add_routes @router, [
  103. Router::Strexp.new("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
  104. Router::Strexp.new("/whois/:id(.:format)", {}, ['/', '.', '?'])
  105. ]
  106. env = rails_env 'PATH_INFO' => '/whois/example.com'
  107. list = []
  108. @router.recognize(env) do |r, _, params|
  109. list << r
  110. end
  111. assert_equal 2, list.length
  112. r = list.first
  113. assert_equal '/whois/:domain', r.path.spec.to_s
  114. end
  115. def test_required_parts_verified_are_anchored
  116. add_routes @router, [
  117. Router::Strexp.new("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
  118. ]
  119. assert_raises(Router::RoutingError) do
  120. @formatter.generate(:path_info, nil, { :id => '10' }, { })
  121. end
  122. end
  123. def test_required_parts_are_verified_when_building
  124. add_routes @router, [
  125. Router::Strexp.new("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
  126. ]
  127. path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
  128. assert_equal '/foo/10', path
  129. assert_raises(Router::RoutingError) do
  130. @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
  131. end
  132. end
  133. def test_only_required_parts_are_verified
  134. add_routes @router, [
  135. Router::Strexp.new("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
  136. ]
  137. path, _ = @formatter.generate(:path_info, nil, { :id => '10' }, { })
  138. assert_equal '/foo/10', path
  139. path, _ = @formatter.generate(:path_info, nil, { }, { })
  140. assert_equal '/foo', path
  141. path, _ = @formatter.generate(:path_info, nil, { :id => 'aa' }, { })
  142. assert_equal '/foo/aa', path
  143. end
  144. def test_X_Cascade
  145. add_routes @router, [ "/messages(.:format)" ]
  146. resp = @router.call({ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/lol' })
  147. assert_equal ['Not Found'], resp.last
  148. assert_equal 'pass', resp[1]['X-Cascade']
  149. assert_equal 404, resp.first
  150. end
  151. def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
  152. strexp = Router::Strexp.new("/", {}, ['/', '.', '?'], false)
  153. path = Path::Pattern.new strexp
  154. app = lambda { |env| [200, {}, ['success!']] }
  155. @router.routes.add_route(app, path, {}, {}, {})
  156. env = rack_env('SCRIPT_NAME' => '', 'PATH_INFO' => '/weblog')
  157. resp = @router.call(env)
  158. assert_equal ['success!'], resp.last
  159. assert_equal '', env['SCRIPT_NAME']
  160. assert_equal '/weblog', env['PATH_INFO']
  161. end
  162. def test_defaults_merge_correctly
  163. path = Path::Pattern.new '/foo(/:id)'
  164. @router.routes.add_route nil, path, {}, {:id => nil}, {}
  165. env = rails_env 'PATH_INFO' => '/foo/10'
  166. @router.recognize(env) do |r, _, params|
  167. assert_equal({:id => '10'}, params)
  168. end
  169. env = rails_env 'PATH_INFO' => '/foo'
  170. @router.recognize(env) do |r, _, params|
  171. assert_equal({:id => nil}, params)
  172. end
  173. end
  174. def test_recognize_with_unbound_regexp
  175. add_routes @router, [
  176. Router::Strexp.new("/foo", { }, ['/', '.', '?'], false)
  177. ]
  178. env = rails_env 'PATH_INFO' => '/foo/bar'
  179. @router.recognize(env) { |*_| }
  180. assert_equal '/foo', env.env['SCRIPT_NAME']
  181. assert_equal '/bar', env.env['PATH_INFO']
  182. end
  183. def test_bound_regexp_keeps_path_info
  184. add_routes @router, [
  185. Router::Strexp.new("/foo", { }, ['/', '.', '?'], true)
  186. ]
  187. env = rails_env 'PATH_INFO' => '/foo'
  188. before = env.env['SCRIPT_NAME']
  189. @router.recognize(env) { |*_| }
  190. assert_equal before, env.env['SCRIPT_NAME']
  191. assert_equal '/foo', env.env['PATH_INFO']
  192. end
  193. def test_path_not_found
  194. add_routes @router, [
  195. "/messages(.:format)",
  196. "/messages/new(.:format)",
  197. "/messages/:id/edit(.:format)",
  198. "/messages/:id(.:format)"
  199. ]
  200. env = rails_env 'PATH_INFO' => '/messages/unknown/path'
  201. yielded = false
  202. @router.recognize(env) do |*whatever|
  203. yielded = true
  204. end
  205. refute yielded
  206. end
  207. def test_required_part_in_recall
  208. add_routes @router, [ "/messages/:a/:b" ]
  209. path, _ = @formatter.generate(:path_info, nil, { :a => 'a' }, { :b => 'b' })
  210. assert_equal "/messages/a/b", path
  211. end
  212. def test_splat_in_recall
  213. add_routes @router, [ "/*path" ]
  214. path, _ = @formatter.generate(:path_info, nil, { }, { :path => 'b' })
  215. assert_equal "/b", path
  216. end
  217. def test_recall_should_be_used_when_scoring
  218. add_routes @router, [
  219. "/messages/:action(/:id(.:format))",
  220. "/messages/:id(.:format)"
  221. ]
  222. path, _ = @formatter.generate(:path_info, nil, { :id => 10 }, { :action => 'index' })
  223. assert_equal "/messages/index/10", path
  224. end
  225. def test_nil_path_parts_are_ignored
  226. path = Path::Pattern.new "/:controller(/:action(.:format))"
  227. @router.routes.add_route nil, path, {}, {}, {}
  228. params = { :controller => "tasks", :format => nil }
  229. extras = { :action => 'lol' }
  230. path, _ = @formatter.generate(:path_info, nil, params, extras)
  231. assert_equal '/tasks', path
  232. end
  233. def test_generate_slash
  234. params = [ [:controller, "tasks"],
  235. [:action, "show"] ]
  236. str = Router::Strexp.new("/", Hash[params], ['/', '.', '?'], true)
  237. path = Path::Pattern.new str
  238. @router.routes.add_route nil, path, {}, {}, {}
  239. path, _ = @formatter.generate(:path_info, nil, Hash[params], {})
  240. assert_equal '/', path
  241. end
  242. def test_generate_calls_param_proc
  243. path = Path::Pattern.new '/:controller(/:action)'
  244. @router.routes.add_route nil, path, {}, {}, {}
  245. parameterized = []
  246. params = [ [:controller, "tasks"],
  247. [:action, "show"] ]
  248. @formatter.generate(
  249. :path_info,
  250. nil,
  251. Hash[params],
  252. {},
  253. lambda { |k,v| parameterized << [k,v]; v })
  254. assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
  255. end
  256. def test_generate_id
  257. path = Path::Pattern.new '/:controller(/:action)'
  258. @router.routes.add_route nil, path, {}, {}, {}
  259. path, params = @formatter.generate(
  260. :path_info, nil, {:id=>1, :controller=>"tasks", :action=>"show"}, {})
  261. assert_equal '/tasks/show', path
  262. assert_equal({:id => 1}, params)
  263. end
  264. def test_generate_escapes
  265. path = Path::Pattern.new '/:controller(/:action)'
  266. @router.routes.add_route nil, path, {}, {}, {}
  267. path, _ = @formatter.generate(:path_info,
  268. nil, { :controller => "tasks",
  269. :action => "a/b c+d",
  270. }, {})
  271. assert_equal '/tasks/a/b%20c+d', path
  272. end
  273. def test_generate_extra_params
  274. path = Path::Pattern.new '/:controller(/:action)'
  275. @router.routes.add_route nil, path, {}, {}, {}
  276. path, params = @formatter.generate(:path_info,
  277. nil, { :id => 1,
  278. :controller => "tasks",
  279. :action => "show",
  280. :relative_url_root => nil
  281. }, {})
  282. assert_equal '/tasks/show', path
  283. assert_equal({:id => 1, :relative_url_root => nil}, params)
  284. end
  285. def test_generate_uses_recall_if_needed
  286. path = Path::Pattern.new '/:controller(/:action(/:id))'
  287. @router.routes.add_route nil, path, {}, {}, {}
  288. path, params = @formatter.generate(:path_info,
  289. nil,
  290. {:controller =>"tasks", :id => 10},
  291. {:action =>"index"})
  292. assert_equal '/tasks/index/10', path
  293. assert_equal({}, params)
  294. end
  295. def test_generate_with_name
  296. path = Path::Pattern.new '/:controller(/:action)'
  297. @router.routes.add_route nil, path, {}, {}, {}
  298. path, params = @formatter.generate(:path_info,
  299. "tasks",
  300. {:controller=>"tasks"},
  301. {:controller=>"tasks", :action=>"index"})
  302. assert_equal '/tasks', path
  303. assert_equal({}, params)
  304. end
  305. {
  306. '/content' => { :controller => 'content' },
  307. '/content/list' => { :controller => 'content', :action => 'list' },
  308. '/content/show/10' => { :controller => 'content', :action => 'show', :id => "10" },
  309. }.each do |request_path, expected|
  310. define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
  311. path = Path::Pattern.new "/:controller(/:action(/:id))"
  312. app = Object.new
  313. route = @router.routes.add_route(app, path, {}, {}, {})
  314. env = rails_env 'PATH_INFO' => request_path
  315. called = false
  316. @router.recognize(env) do |r, _, params|
  317. assert_equal route, r
  318. assert_equal(expected, params)
  319. called = true
  320. end
  321. assert called
  322. end
  323. end
  324. {
  325. :segment => ['/a%2Fb%20c+d/splat', { :segment => 'a/b c+d', :splat => 'splat' }],
  326. :splat => ['/segment/a/b%20c+d', { :segment => 'segment', :splat => 'a/b c+d' }]
  327. }.each do |name, (request_path, expected)|
  328. define_method("test_recognize_#{name}") do
  329. path = Path::Pattern.new '/:segment/*splat'
  330. app = Object.new
  331. route = @router.routes.add_route(app, path, {}, {}, {})
  332. env = rails_env 'PATH_INFO' => request_path
  333. called = false
  334. @router.recognize(env) do |r, _, params|
  335. assert_equal route, r
  336. assert_equal(expected, params)
  337. called = true
  338. end
  339. assert called
  340. end
  341. end
  342. def test_namespaced_controller
  343. strexp = Router::Strexp.new(
  344. "/:controller(/:action(/:id))",
  345. { :controller => /.+?/ },
  346. ["/", ".", "?"]
  347. )
  348. path = Path::Pattern.new strexp
  349. app = Object.new
  350. route = @router.routes.add_route(app, path, {}, {}, {})
  351. env = rails_env 'PATH_INFO' => '/admin/users/show/10'
  352. called = false
  353. expected = {
  354. :controller => 'admin/users',
  355. :action => 'show',
  356. :id => '10'
  357. }
  358. @router.recognize(env) do |r, _, params|
  359. assert_equal route, r
  360. assert_equal(expected, params)
  361. called = true
  362. end
  363. assert called
  364. end
  365. def test_recognize_literal
  366. path = Path::Pattern.new "/books(/:action(.:format))"
  367. app = Object.new
  368. route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
  369. env = rails_env 'PATH_INFO' => '/books/list.rss'
  370. expected = { :controller => 'books', :action => 'list', :format => 'rss' }
  371. called = false
  372. @router.recognize(env) do |r, _, params|
  373. assert_equal route, r
  374. assert_equal(expected, params)
  375. called = true
  376. end
  377. assert called
  378. end
  379. def test_recognize_cares_about_verbs
  380. path = Path::Pattern.new "/books(/:action(.:format))"
  381. app = Object.new
  382. conditions = {
  383. :request_method => 'GET'
  384. }
  385. @router.routes.add_route(app, path, conditions, {})
  386. conditions = conditions.dup
  387. conditions[:request_method] = 'POST'
  388. post = @router.routes.add_route(app, path, conditions, {})
  389. env = rails_env 'PATH_INFO' => '/books/list.rss',
  390. "REQUEST_METHOD" => "POST"
  391. called = false
  392. @router.recognize(env) do |r, _, params|
  393. assert_equal post, r
  394. called = true
  395. end
  396. assert called
  397. end
  398. private
  399. def add_routes router, paths
  400. paths.each do |path|
  401. path = Path::Pattern.new path
  402. router.routes.add_route nil, path, {}, {}, {}
  403. end
  404. end
  405. RailsEnv = Struct.new(:env)
  406. def rails_env env
  407. RailsEnv.new rack_env env
  408. end
  409. def rack_env env
  410. {
  411. "rack.version" => [1, 1],
  412. "rack.input" => StringIO.new,
  413. "rack.errors" => StringIO.new,
  414. "rack.multithread" => true,
  415. "rack.multiprocess" => true,
  416. "rack.run_once" => false,
  417. "REQUEST_METHOD" => "GET",
  418. "SERVER_NAME" => "example.org",
  419. "SERVER_PORT" => "80",
  420. "QUERY_STRING" => "",
  421. "PATH_INFO" => "/content",
  422. "rack.url_scheme" => "http",
  423. "HTTPS" => "off",
  424. "SCRIPT_NAME" => "",
  425. "CONTENT_LENGTH" => "0"
  426. }.merge env
  427. end
  428. end
  429. end