lotus-router 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.gitignore +5 -13
- data/.travis.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -3
- data/README.md +470 -6
- data/Rakefile +10 -1
- data/benchmarks/callable +23 -0
- data/benchmarks/named_routes +72 -0
- data/benchmarks/resource +44 -0
- data/benchmarks/resources +58 -0
- data/benchmarks/routes +67 -0
- data/benchmarks/run.sh +11 -0
- data/benchmarks/utils.rb +56 -0
- data/lib/lotus-router.rb +1 -0
- data/lib/lotus/router.rb +752 -3
- data/lib/lotus/router/version.rb +2 -2
- data/lib/lotus/routing/endpoint.rb +114 -0
- data/lib/lotus/routing/endpoint_resolver.rb +251 -0
- data/lib/lotus/routing/http_router.rb +130 -0
- data/lib/lotus/routing/namespace.rb +86 -0
- data/lib/lotus/routing/resource.rb +73 -0
- data/lib/lotus/routing/resource/action.rb +340 -0
- data/lib/lotus/routing/resource/options.rb +48 -0
- data/lib/lotus/routing/resources.rb +40 -0
- data/lib/lotus/routing/resources/action.rb +123 -0
- data/lib/lotus/routing/route.rb +53 -0
- data/lotus-router.gemspec +16 -12
- data/test/fixtures.rb +193 -0
- data/test/integration/client_error_test.rb +16 -0
- data/test/integration/pass_on_response_test.rb +13 -0
- data/test/named_routes_test.rb +123 -0
- data/test/namespace_test.rb +289 -0
- data/test/new_test.rb +67 -0
- data/test/redirect_test.rb +33 -0
- data/test/resource_test.rb +128 -0
- data/test/resources_test.rb +136 -0
- data/test/routing/endpoint_resolver_test.rb +110 -0
- data/test/routing/resource/options_test.rb +36 -0
- data/test/routing_test.rb +99 -0
- data/test/test_helper.rb +32 -0
- data/test/version_test.rb +7 -0
- 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
|