lotus-router 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +5 -13
  4. data/.travis.yml +5 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +10 -3
  7. data/README.md +470 -6
  8. data/Rakefile +10 -1
  9. data/benchmarks/callable +23 -0
  10. data/benchmarks/named_routes +72 -0
  11. data/benchmarks/resource +44 -0
  12. data/benchmarks/resources +58 -0
  13. data/benchmarks/routes +67 -0
  14. data/benchmarks/run.sh +11 -0
  15. data/benchmarks/utils.rb +56 -0
  16. data/lib/lotus-router.rb +1 -0
  17. data/lib/lotus/router.rb +752 -3
  18. data/lib/lotus/router/version.rb +2 -2
  19. data/lib/lotus/routing/endpoint.rb +114 -0
  20. data/lib/lotus/routing/endpoint_resolver.rb +251 -0
  21. data/lib/lotus/routing/http_router.rb +130 -0
  22. data/lib/lotus/routing/namespace.rb +86 -0
  23. data/lib/lotus/routing/resource.rb +73 -0
  24. data/lib/lotus/routing/resource/action.rb +340 -0
  25. data/lib/lotus/routing/resource/options.rb +48 -0
  26. data/lib/lotus/routing/resources.rb +40 -0
  27. data/lib/lotus/routing/resources/action.rb +123 -0
  28. data/lib/lotus/routing/route.rb +53 -0
  29. data/lotus-router.gemspec +16 -12
  30. data/test/fixtures.rb +193 -0
  31. data/test/integration/client_error_test.rb +16 -0
  32. data/test/integration/pass_on_response_test.rb +13 -0
  33. data/test/named_routes_test.rb +123 -0
  34. data/test/namespace_test.rb +289 -0
  35. data/test/new_test.rb +67 -0
  36. data/test/redirect_test.rb +33 -0
  37. data/test/resource_test.rb +128 -0
  38. data/test/resources_test.rb +136 -0
  39. data/test/routing/endpoint_resolver_test.rb +110 -0
  40. data/test/routing/resource/options_test.rb +36 -0
  41. data/test/routing_test.rb +99 -0
  42. data/test/test_helper.rb +32 -0
  43. data/test/version_test.rb +7 -0
  44. metadata +102 -10
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ end
7
+
8
+ describe '#redirect' do
9
+ it 'recognizes string endpoint' do
10
+ endpoint = ->(env) { [200, {}, ['Redirect destination!']] }
11
+ @router.get('/redirect_destination', to: endpoint)
12
+ @router.redirect('/redirect', to: '/redirect_destination')
13
+
14
+ env = Rack::MockRequest.env_for('/redirect')
15
+ status, headers, _ = @router.call(env)
16
+
17
+ status.must_equal 302
18
+ headers['Location'].must_equal '/redirect_destination'
19
+ end
20
+
21
+ it 'recognizes string endpoint with custom http code' do
22
+ endpoint = ->(env) { [200, {}, ['Redirect destination!']] }
23
+ @router.get('/redirect_destination', to: endpoint)
24
+ @router.redirect('/redirect', to: '/redirect_destination', code: 301)
25
+
26
+ env = Rack::MockRequest.env_for('/redirect')
27
+ status, headers, _ = @router.call(env)
28
+
29
+ status.must_equal 301
30
+ headers['Location'].must_equal '/redirect_destination'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,128 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ @app = Rack::MockRequest.new(@router)
7
+ end
8
+
9
+ after do
10
+ @router.reset!
11
+ end
12
+
13
+ def endpoint(response)
14
+ ->(env) { response }
15
+ end
16
+
17
+ describe '#resource' do
18
+ before do
19
+ @router.resource 'avatar'
20
+ end
21
+
22
+ it 'recognizes get new' do
23
+ @router.path(:new_avatar).must_equal '/avatar/new'
24
+ @app.request('GET', '/avatar/new').body.must_equal 'Avatar::New'
25
+ end
26
+
27
+ it 'recognizes post create' do
28
+ @router.path(:avatar).must_equal '/avatar'
29
+ @app.request('POST', '/avatar').body.must_equal 'Avatar::Create'
30
+ end
31
+
32
+ it 'recognizes get show' do
33
+ @router.path(:avatar).must_equal '/avatar'
34
+ @app.request('GET', '/avatar').body.must_equal 'Avatar::Show'
35
+ end
36
+
37
+ it 'recognizes get edit' do
38
+ @router.path(:edit_avatar).must_equal '/avatar/edit'
39
+ @app.request('GET', '/avatar/edit').body.must_equal 'Avatar::Edit'
40
+ end
41
+
42
+ it 'recognizes patch update' do
43
+ @router.path(:avatar).must_equal '/avatar'
44
+ @app.request('PATCH', '/avatar').body.must_equal 'Avatar::Update'
45
+ end
46
+
47
+ it 'recognizes delete destroy' do
48
+ @router.path(:avatar).must_equal '/avatar'
49
+ @app.request('DELETE', '/avatar').body.must_equal 'Avatar::Destroy'
50
+ end
51
+
52
+ describe ':only option' do
53
+ before do
54
+ @router.resource 'profile', only: [:edit, :update]
55
+ end
56
+
57
+ it 'recognizes only specified paths' do
58
+ @router.path(:edit_profile).must_equal '/profile/edit'
59
+ @app.request('GET', '/profile/edit').body.must_equal 'Profile::Edit'
60
+
61
+ @router.path(:profile).must_equal '/profile'
62
+ @app.request('PATCH', '/profile').body.must_equal 'Profile::Update'
63
+ end
64
+
65
+ it 'does not recognize other paths' do
66
+ @app.request('GET', '/profile/new').status.must_equal 405
67
+ @app.request('POST', '/profile').status.must_equal 405
68
+ @app.request('GET', '/profile').status.must_equal 405
69
+ @app.request('DELETE', '/profile').status.must_equal 405
70
+
71
+ -> { @router.path(:new_profile) }.must_raise HttpRouter::InvalidRouteException
72
+ end
73
+ end
74
+
75
+ describe ':except option' do
76
+ before do
77
+ @router.resource 'profile', except: [:new, :show, :create, :destroy]
78
+ end
79
+
80
+ it 'recognizes only the non-rejected paths' do
81
+ @router.path(:edit_profile).must_equal '/profile/edit'
82
+ @app.request('GET', '/profile/edit').body.must_equal 'Profile::Edit'
83
+
84
+ @router.path(:profile).must_equal '/profile'
85
+ @app.request('PATCH', '/profile').body.must_equal 'Profile::Update'
86
+ end
87
+
88
+ it 'does not recognize other paths' do
89
+ @app.request('GET', '/profile/new').status.must_equal 405
90
+ @app.request('POST', '/profile').status.must_equal 405
91
+ @app.request('GET', '/profile').status.must_equal 405
92
+ @app.request('DELETE', '/profile').status.must_equal 405
93
+
94
+ -> { @router.path(:new_profile) }.must_raise HttpRouter::InvalidRouteException
95
+ end
96
+ end
97
+
98
+ describe 'member' do
99
+ before do
100
+ @router.resource 'profile', only: [] do
101
+ member do
102
+ patch 'activate'
103
+ end
104
+ end
105
+ end
106
+
107
+ it 'recognizes the path' do
108
+ @router.path(:activate_profile).must_equal '/profile/activate'
109
+ @app.request('PATCH', '/profile/activate').body.must_equal 'Profile::Activate'
110
+ end
111
+ end
112
+
113
+ describe 'collection' do
114
+ before do
115
+ @router.resource 'profile', only: [] do
116
+ collection do
117
+ get 'keys'
118
+ end
119
+ end
120
+ end
121
+
122
+ it 'recognizes the path' do
123
+ @router.path(:keys_profile).must_equal '/profile/keys'
124
+ @app.request('GET', '/profile/keys').body.must_equal 'Profile::Keys'
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,136 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Router do
4
+ before do
5
+ @router = Lotus::Router.new
6
+ @app = Rack::MockRequest.new(@router)
7
+ end
8
+
9
+ after do
10
+ @router.reset!
11
+ end
12
+
13
+ def endpoint(response)
14
+ ->(env) { response }
15
+ end
16
+
17
+ describe '#resources' do
18
+ before do
19
+ @router.resources 'flowers'
20
+ end
21
+
22
+ it 'recognizes get index' do
23
+ @router.path(:flowers).must_equal '/flowers'
24
+ @app.request('GET', '/flowers').body.must_equal 'Flowers::Index'
25
+ end
26
+
27
+ it 'recognizes get new' do
28
+ @router.path(:new_flowers).must_equal '/flowers/new'
29
+ @app.request('GET', '/flowers/new').body.must_equal 'Flowers::New'
30
+ end
31
+
32
+ it 'recognizes post create' do
33
+ @router.path(:flowers).must_equal '/flowers'
34
+ @app.request('POST', '/flowers').body.must_equal 'Flowers::Create'
35
+ end
36
+
37
+ it 'recognizes get show' do
38
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
39
+ @app.request('GET', '/flowers/23').body.must_equal 'Flowers::Show 23'
40
+ end
41
+
42
+ it 'recognizes get edit' do
43
+ @router.path(:edit_flowers, id: 23).must_equal '/flowers/23/edit'
44
+ @app.request('GET', '/flowers/23/edit').body.must_equal 'Flowers::Edit 23'
45
+ end
46
+
47
+ it 'recognizes patch update' do
48
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
49
+ @app.request('PATCH', '/flowers/23').body.must_equal 'Flowers::Update 23'
50
+ end
51
+
52
+ it 'recognizes delete destroy' do
53
+ @router.path(:flowers, id: 23).must_equal '/flowers/23'
54
+ @app.request('DELETE', '/flowers/23').body.must_equal 'Flowers::Destroy 23'
55
+ end
56
+
57
+ describe ':only option' do
58
+ before do
59
+ @router.resources 'keyboards', only: [:index, :edit]
60
+ end
61
+
62
+ it 'recognizes only specified paths' do
63
+ @router.path(:keyboards).must_equal '/keyboards'
64
+ @app.request('GET', '/keyboards').body.must_equal 'Keyboards::Index'
65
+
66
+ @router.path(:edit_keyboards, id: 23).must_equal '/keyboards/23/edit'
67
+ @app.request('GET', '/keyboards/23/edit').body.must_equal 'Keyboards::Edit 23'
68
+ end
69
+
70
+ it 'does not recognize other paths' do
71
+ @app.request('GET', '/keyboards/new').status.must_equal 404
72
+ @app.request('POST', '/keyboards').status.must_equal 405
73
+ @app.request('GET', '/keyboards/23').status.must_equal 404
74
+ @app.request('PATCH', '/keyboards/23').status.must_equal 405
75
+ @app.request('DELETE', '/keyboards/23').status.must_equal 405
76
+
77
+ -> { @router.path(:new_keyboards) }.must_raise HttpRouter::InvalidRouteException
78
+ end
79
+ end
80
+
81
+ describe ':except option' do
82
+ before do
83
+ @router.resources 'keyboards', except: [:new, :show, :update, :destroy]
84
+ end
85
+
86
+ it 'recognizes only the non-rejected paths' do
87
+ @router.path(:keyboards).must_equal '/keyboards'
88
+ @app.request('GET', '/keyboards').body.must_equal 'Keyboards::Index'
89
+
90
+ @router.path(:edit_keyboards, id: 23).must_equal '/keyboards/23/edit'
91
+ @app.request('GET', '/keyboards/23/edit').body.must_equal 'Keyboards::Edit 23'
92
+
93
+ @router.path(:keyboards).must_equal '/keyboards'
94
+ @app.request('POST', '/keyboards').body.must_equal 'Keyboards::Create'
95
+ end
96
+
97
+ it 'does not recognize other paths' do
98
+ @app.request('GET', '/keyboards/new').status.must_equal 404
99
+ @app.request('PATCH', '/keyboards/23').status.must_equal 405
100
+ @app.request('DELETE', '/keyboards/23').status.must_equal 405
101
+
102
+ -> { @router.path(:new_keyboards) }.must_raise HttpRouter::InvalidRouteException
103
+ end
104
+ end
105
+
106
+ describe 'member' do
107
+ before do
108
+ @router.resources 'keyboards', only: [] do
109
+ member do
110
+ get 'screenshot'
111
+ end
112
+ end
113
+ end
114
+
115
+ it 'recognizes the path' do
116
+ @router.path(:screenshot_keyboards, id: 23).must_equal '/keyboards/23/screenshot'
117
+ @app.request('GET', '/keyboards/23/screenshot').body.must_equal 'Keyboards::Screenshot 23'
118
+ end
119
+ end
120
+
121
+ describe 'collection' do
122
+ before do
123
+ @router.resources 'keyboards', only: [] do
124
+ collection do
125
+ get 'search'
126
+ end
127
+ end
128
+ end
129
+
130
+ it 'recognizes the path' do
131
+ @router.path(:search_keyboards).must_equal '/keyboards/search'
132
+ @app.request('GET', '/keyboards/search').body.must_equal 'Keyboards::Search'
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,110 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Routing::EndpointResolver do
4
+ before do
5
+ @resolver = Lotus::Routing::EndpointResolver.new
6
+ end
7
+
8
+ it 'recognizes :to when it is a callable object' do
9
+ endpoint = Object.new
10
+ endpoint.define_singleton_method(:call) { }
11
+
12
+ options = { to: endpoint }
13
+
14
+ @resolver.resolve(options).must_equal(endpoint)
15
+ end
16
+
17
+ it 'recognizes :to when it is a string that references a class that can be retrieved now' do
18
+ options = { to: 'test_endpoint' }
19
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestEndpoint!'
20
+ end
21
+
22
+ describe 'when :to references a missing class' do
23
+ it 'if the class is available when invoking call, it succeed' do
24
+ options = { to: 'lazy_controller' }
25
+ endpoint = @resolver.resolve(options)
26
+ LazyController = Class.new(Object) { define_method(:call) {|env| env } }
27
+
28
+ endpoint.call({}).must_equal({})
29
+ end
30
+
31
+ it 'if the class is not available when invoking call, it raises error' do
32
+ options = { to: 'missing_endpoint' }
33
+ endpoint = @resolver.resolve(options)
34
+
35
+ -> { endpoint.call({}) }.must_raise Lotus::Routing::EndpointNotFound
36
+ end
37
+ end
38
+
39
+ it 'recognizes :to when it is a string with separator' do
40
+ options = { to: 'test#show' }
41
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
42
+ end
43
+
44
+ it 'returns the default endpoint when cannot match anything' do
45
+ options = { to: 23 }
46
+ @resolver.resolve(options).call({}).first.must_equal 404
47
+ end
48
+
49
+ describe 'namespace' do
50
+ before do
51
+ @resolver = Lotus::Routing::EndpointResolver.new(namespace: TestApp)
52
+ end
53
+
54
+ it 'recognizes :to when it is a string and an explicit namespace' do
55
+ options = { to: 'test_endpoint' }
56
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestApp::TestEndpoint!'
57
+ end
58
+
59
+ it 'recognizes :to when it is a string with separator and it has an explicit namespace' do
60
+ options = { to: 'test2#show' }
61
+ @resolver.resolve(options).call({}).must_equal 'Hi from TestApp::Test2Controller::Show!'
62
+ end
63
+ end
64
+
65
+ describe 'custom endpoint' do
66
+ before :all do
67
+ class CustomEndpoint
68
+ def initialize(endpoint)
69
+ @endpoint = endpoint
70
+ end
71
+ end
72
+
73
+ @resolver = Lotus::Routing::EndpointResolver.new(endpoint: CustomEndpoint)
74
+ end
75
+
76
+ after do
77
+ Object.send(:remove_const, :CustomEndpoint)
78
+ end
79
+
80
+ it 'returns specified endpoint instance' do
81
+ @resolver.resolve({}).class.must_equal(CustomEndpoint)
82
+ end
83
+ end
84
+
85
+ describe 'custom separator' do
86
+ before do
87
+ @resolver = Lotus::Routing::EndpointResolver.new(action_separator: action_separator)
88
+ end
89
+
90
+ let(:action_separator) { '@' }
91
+
92
+ it 'matches controller and action with a custom separator' do
93
+ options = { to: "test#{ action_separator }show" }
94
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
95
+ end
96
+ end
97
+
98
+ describe 'custom suffix' do
99
+ before do
100
+ @resolver = Lotus::Routing::EndpointResolver.new(suffix: suffix)
101
+ end
102
+
103
+ let(:suffix) { 'Controller::' }
104
+
105
+ it 'matches controller and action with a custom separator' do
106
+ options = { to: 'test#show' }
107
+ @resolver.resolve(options).call({}).must_equal 'Hi from Test::Show!'
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Routing::Resource::Options do
4
+ before do
5
+ @actions = [:index, :new, :create, :show, :edit, :update, :destroy]
6
+ end
7
+
8
+ it 'returns all the actions when no exceptions are requested' do
9
+ Lotus::Routing::Resources::Options.new(@actions).actions.must_equal @actions
10
+ end
11
+
12
+ it 'returns only the action requested via the :only option' do
13
+ options = Lotus::Routing::Resources::Options.new(@actions, only: :show)
14
+ options.actions.must_equal [:show]
15
+ end
16
+
17
+ it 'returns only the actions requested via the :only options' do
18
+ options = Lotus::Routing::Resources::Options.new(@actions, only: [:create, :edit])
19
+ options.actions.must_equal [:create, :edit]
20
+ end
21
+
22
+ it 'returns only the action not rejected via the :except option' do
23
+ options = Lotus::Routing::Resources::Options.new(@actions, except: :destroy)
24
+ options.actions.must_equal [:index, :new, :create, :show, :edit, :update]
25
+ end
26
+
27
+ it 'returns only the actions requested via the :only options' do
28
+ options = Lotus::Routing::Resources::Options.new(@actions, except: [:index, :new, :edit])
29
+ options.actions.must_equal [:create, :show, :update, :destroy]
30
+ end
31
+
32
+ it 'allow access to values' do
33
+ options = Lotus::Routing::Resources::Options.new(@actions, name: :lotus)
34
+ options[:name].must_equal :lotus
35
+ end
36
+ end