rutter 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.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Rack integration" do
6
+ let(:router) do
7
+ Rutter.new do
8
+ get "/books", to: ->(_env) { [200, {}, ["Books"]] }
9
+ end
10
+ end
11
+
12
+ def app
13
+ router.freeze
14
+ end
15
+
16
+ describe "with match" do
17
+ it "calls the matched endpoint" do
18
+ get "/books"
19
+ assert_equal 200, last_response.status
20
+ assert_equal "Books", last_response.body
21
+ end
22
+ end
23
+
24
+ describe "with no match" do
25
+ it "returns 404" do
26
+ get "/authors"
27
+ assert_equal 404, last_response.status
28
+ assert_equal "Route Not Found", last_response.body
29
+ assert_equal "pass", last_response.headers["X-Cascade"]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Rutter::Builder#redirect" do
6
+ let(:router) do
7
+ Rutter.new do
8
+ get "/legacy-path", to: redirect("/new_path")
9
+ get "/legacy-path-custom-status", to: redirect("/new_path", status: 301)
10
+ get "/new_path", to: ->(_env) { [200, {}, ["Redirected"]] }
11
+ end
12
+ end
13
+
14
+ def app
15
+ router.freeze
16
+ end
17
+
18
+ it "redirect the request to the new path" do
19
+ get "/legacy-path"
20
+ assert_equal 302, last_response.status
21
+ assert_equal "", last_response.body
22
+ follow_redirect!
23
+ assert_equal 200, last_response.status
24
+ assert_equal "Redirected", last_response.body
25
+ end
26
+
27
+ it "support setting custom status" do
28
+ get "/legacy-path-custom-status"
29
+ assert_equal 301, last_response.status
30
+ follow_redirect!
31
+ assert_equal 200, last_response.status
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ if ENV["COVERAGE"] == "true"
6
+ require "simplecov"
7
+ require "codeclimate-test-reporter"
8
+
9
+ SimpleCov.start do
10
+ command_name "spec"
11
+ add_filter "spec"
12
+ end
13
+ end
14
+
15
+ require "minitest/autorun"
16
+ require "minitest/pride"
17
+ require "rack/test"
18
+ require "mocha/mini_test"
19
+
20
+ # Require support (helper) modules
21
+ Dir["./spec/support/**/*.rb"].each { |f| require f }
22
+
23
+ require "rutter"
24
+
25
+ class Minitest::Spec
26
+ include Rack::Test::Methods
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minitest::Assertions
4
+ def assert_file_exist(file)
5
+ assert File.exist?(file), "Expected #{file} to be a file"
6
+ end
7
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Rutter
6
+ describe Builder do
7
+ let(:router) { Builder.new }
8
+
9
+ describe "#add" do
10
+ it "returns the created route object" do
11
+ route = router.get "/books", to: ->(env) {}
12
+ named_route = router.get "/named_books", to: ->(env) {}, as: :books
13
+ assert_instance_of Route, route
14
+ assert_instance_of Route, named_route
15
+ end
16
+
17
+ it "adds a route to the collection" do
18
+ router.get "/books", to: ->(env) {}
19
+ router.post "/books", to: ->(env) {}
20
+
21
+ assert_equal 2, router.flat_map.size
22
+ end
23
+
24
+ it "stores the name of the route" do
25
+ route = router.add "GET", "/books", as: :books, to: ->(env) {}
26
+ assert_equal route, router.named_map[:books]
27
+ end
28
+
29
+ it "normalizes route names" do
30
+ router.get "/books", as: :_bookie__books__, to: ->(env) {}
31
+ assert router.named_map.key?(:bookie_books)
32
+ end
33
+
34
+ it "raises an error if named route already been defined" do
35
+ router.get "/books", as: :books, to: ->(env) {}
36
+
37
+ err = assert_raises RuntimeError do
38
+ router.get "/books", as: :books, to: ->(env) {}
39
+ end
40
+
41
+ assert_equal "a route called 'books' has already been defined",
42
+ err.message
43
+ end
44
+ end
45
+
46
+ describe "#root" do
47
+ it "adds a root path with a root name" do
48
+ route = router.root to: ->(env) {}
49
+ assert_equal "/", route.path
50
+ assert_equal route, router.named_map[:root]
51
+ end
52
+ end
53
+
54
+ describe "verbs" do
55
+ Route::VERBS.each do |verb|
56
+ it "accept the #{verb} verb as method" do
57
+ router.send verb.downcase, "/", to: ->() {}
58
+ assert_equal verb, router.flat_map[0].method
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#path" do
64
+ it "generates a URL path from the arguments" do
65
+ router.get "/users/:id(/:username)", to: "Users#show", as: :user
66
+ assert_equal "/users/45/sandelius",
67
+ router.path(:user, id: 45, username: "sandelius")
68
+ assert_equal "/users/45", router.path(:user, id: 45)
69
+ end
70
+
71
+ it "raises an error if route has not been defined" do
72
+ err = assert_raises ArgumentError do
73
+ router.path(:user)
74
+ end
75
+ assert_equal "no route called 'user'", err.message
76
+ end
77
+ end
78
+
79
+ describe "#url" do
80
+ it "generates a full URL" do
81
+ router.get "/users/:id", to: "Users#show", as: :user
82
+ assert_equal "http://example.com/users/54", router.url(:user, id: 54)
83
+ end
84
+
85
+ it "allows host scheme and subdomain to be set" do
86
+ router.get "/users/:id", to: "Users#show", as: :user
87
+ assert_equal(
88
+ "https://api.example.com/users/54",
89
+ router.url(
90
+ :user,
91
+ id: 54, _host: "example.com", _scheme: "https", _subdomain: "api"
92
+ )
93
+ )
94
+ end
95
+
96
+ it "adds port if no 80 or 433" do
97
+ router.get "/users/:id", to: "Users#show", as: :user
98
+ assert_equal(
99
+ "http://example.com:333/users/54",
100
+ router.url(:user, id: 54, _port: 333)
101
+ )
102
+ end
103
+ end
104
+
105
+ describe "#freeze" do
106
+ it "freezes all objects" do
107
+ router.freeze
108
+
109
+ assert router.frozen?
110
+ assert router.flat_map.frozen?
111
+ assert router.named_map.frozen?
112
+ assert router.verb_map.frozen?
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Rutter
6
+ describe Route do
7
+ it "raises an error if request method is invalid" do
8
+ err = assert_raises ArgumentError do
9
+ Route.new("NONE", "/", ->(env) {})
10
+ end
11
+
12
+ assert_equal "invalid verb: 'NONE'", err.message
13
+ end
14
+
15
+ it "normalizes path" do
16
+ route = Route.new("GET", "//about////us/", ->(env) {})
17
+ assert_equal "/about/us", route.path
18
+ end
19
+
20
+ describe "#expand" do
21
+ it "expand dynamic routes" do
22
+ route = Route.new("GET", "/books/:id(/:title)", -> {})
23
+ assert_equal "/books/54/eloquent?include[]=foo",
24
+ route.expand(id: 54, title: "eloquent", include: [:foo])
25
+ assert_equal "/books/54?include[]=foo",
26
+ route.expand(id: 54, include: [:foo])
27
+ assert_equal "/books/54/eloquent",
28
+ route.expand(id: 54, title: "eloquent")
29
+ assert_equal "/books/54", route.expand(id: 54)
30
+ end
31
+
32
+ it "expand static routes" do
33
+ route = Route.new("GET", "/books", -> {})
34
+ assert_equal "/books?include[]=foo", route.expand(include: [:foo])
35
+ assert_equal "/books", route.expand
36
+ end
37
+ end
38
+
39
+ describe "#match?" do
40
+ it "matches non regexp" do
41
+ route = Route.new("GET", "/books", -> {})
42
+ assert route.match?("/books")
43
+ end
44
+
45
+ it "support named parameters" do
46
+ route = Route.new("GET", "/pages/:id(/:title)", -> {})
47
+
48
+ assert route.match?("/pages/54/eloquent-ruby")
49
+ assert route.match?("/pages/54")
50
+ refute route.match?("/pages/54.rb")
51
+ refute route.match?("/pages")
52
+ end
53
+
54
+ it "support catch-all parameter" do
55
+ route = Route.new("GET", "/pages/*slug", -> {})
56
+ assert route.match?("/pages/54/eloquent-ruby")
57
+ assert route.match?("/pages/54/eloquent-ruby.rb")
58
+ assert route.match?("/pages/54/eloquent-ruby/with/more/parts")
59
+ refute route.match?("/pages")
60
+ end
61
+ end
62
+
63
+ describe "#params" do
64
+ it "extract params" do
65
+ route = Route.new("GET", "/pages/:id(/:title)", -> {})
66
+ assert_equal(
67
+ { "id" => "54", "title" => "eloquent-ruby" },
68
+ route.params("/pages/54/eloquent-ruby")
69
+ )
70
+
71
+ assert_equal(
72
+ { "id" => "54", "title" => nil },
73
+ route.params("/pages/54")
74
+ )
75
+ end
76
+
77
+ it "returns nil if no match" do
78
+ route = Route.new("GET", "/books/:id", -> {})
79
+ assert_nil route.params("/posts/54")
80
+ end
81
+
82
+ it "returns empty array if no params present" do
83
+ route = Route.new("GET", "/books", -> {})
84
+ assert_equal({}, route.params("/books"))
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Rutter
6
+ describe Routes do
7
+ let(:router) { Builder.new }
8
+ let(:routes) { Routes.new(router) }
9
+
10
+ it "has a *_path helper method" do
11
+ router.get "/books/:id", to: "Books#show", as: :book
12
+ assert_equal "/books/54", routes.book_path(id: 54)
13
+ end
14
+
15
+ it "has a *_url helper method" do
16
+ router.get "/books/:id", to: "Books#show", as: :book
17
+ assert_equal "http://example.com/books/54",
18
+ routes.book_url(id: 54)
19
+ end
20
+
21
+ it "raises ArgumentError if route not found" do
22
+ assert_raises(ArgumentError) { routes.invalid_path }
23
+ end
24
+
25
+ it "raises NoMethodError if unknown method is called" do
26
+ assert_raises(NoMethodError) { routes.invalid }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe Rutter do
6
+ it "has a version number" do
7
+ assert_instance_of String, Rutter::VERSION
8
+ end
9
+
10
+ describe ".new" do
11
+ it "returns a new Builder object" do
12
+ assert_instance_of Rutter::Builder, Rutter.new
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Rutter
6
+ describe Builder do
7
+ let(:router) { Builder.new }
8
+
9
+ describe "scope" do
10
+ it "prepends prefixes" do
11
+ router.scope path: "web", namespace: "Web" do
12
+ get "/books", to: "Books#index"
13
+ end
14
+
15
+ route = router.flat_map.first
16
+ assert_equal "/web/books", route.path
17
+ assert_equal "Web::Books", route.endpoint[:controller]
18
+ end
19
+
20
+ it "recognizes all verbs" do
21
+ router.scope path: "/admin", namespace: "Secure", as: :koolaid do
22
+ get "/", to: ->(env) {}
23
+ post "/", to: ->(env) {}
24
+ put "/", to: ->(env) {}
25
+ patch "/", to: ->(env) {}
26
+ delete "/", to: ->(env) {}
27
+ options "/", to: ->(env) {}
28
+ head "/", to: ->(env) {}
29
+ trace "/", to: ->(env) {}
30
+ end
31
+
32
+ assert_equal "GET", router.flat_map[0].method
33
+ assert_equal "POST", router.flat_map[1].method
34
+ assert_equal "PUT", router.flat_map[2].method
35
+ assert_equal "PATCH", router.flat_map[3].method
36
+ assert_equal "DELETE", router.flat_map[4].method
37
+ assert_equal "OPTIONS", router.flat_map[5].method
38
+ assert_equal "HEAD", router.flat_map[6].method
39
+ assert_equal "TRACE", router.flat_map[7].method
40
+ end
41
+
42
+ describe "#root" do
43
+ it "adds a root path with a root name" do
44
+ router.scope path: "/admin", namespace: "Secure", as: :koolaid do
45
+ root to: ->(env) {}
46
+ end
47
+
48
+ assert_equal "/admin", router.flat_map.first.path
49
+ assert router.named_map.key?(:koolaid_root)
50
+ end
51
+ end
52
+
53
+ it "support nested scopes" do
54
+ router.scope path: "animals", namespace: "Species", as: :animals do
55
+ scope path: "mammals", namespace: "Mammals", as: :mammals do
56
+ get "/cats", to: "Cats#index", as: :cats
57
+ end
58
+ end
59
+
60
+ route = router.flat_map.first
61
+ assert_equal "/animals/mammals/cats", route.path
62
+ assert_equal "Species::Mammals::Cats", route.endpoint[:controller]
63
+ assert_equal "index", route.endpoint[:action]
64
+ assert router.named_map.key?(:animals_mammals_cats)
65
+ end
66
+
67
+ describe "#mount" do
68
+ it "calls the root mount with path prefix" do
69
+ app = ->(_env) {}
70
+ router.expects(:mount).with(app, at: "/admin/books")
71
+ router.scope(path: "/admin") { mount app, at: "books" }
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rutter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Sandelius
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rack-test
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email: tobias@sandeli.us
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - ".rubocop.yml"
105
+ - ".travis.yml"
106
+ - ".yardopts"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - bench/config.ru
112
+ - bench/dynamic_routes
113
+ - bench/expand
114
+ - bench/helper.rb
115
+ - bench/mount
116
+ - bench/routes_helper
117
+ - bench/static_routes
118
+ - lib/rutter.rb
119
+ - lib/rutter/builder.rb
120
+ - lib/rutter/route.rb
121
+ - lib/rutter/routes.rb
122
+ - lib/rutter/scope.rb
123
+ - lib/rutter/version.rb
124
+ - rutter.gemspec
125
+ - spec/integration/mount_spec.rb
126
+ - spec/integration/params_spec.rb
127
+ - spec/integration/rack_spec.rb
128
+ - spec/integration/redirect_spec.rb
129
+ - spec/spec_helper.rb
130
+ - spec/support/assertions.rb
131
+ - spec/unit/builder_spec.rb
132
+ - spec/unit/route_spec.rb
133
+ - spec/unit/routes_spec.rb
134
+ - spec/unit/rutter_spec.rb
135
+ - spec/unit/scope_spec.rb
136
+ homepage: https://github.com/sandelius/rutter
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: 2.4.0
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: 2.5.0
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.6.11
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: HTTP router for Rack.
160
+ test_files:
161
+ - spec/integration/mount_spec.rb
162
+ - spec/integration/params_spec.rb
163
+ - spec/integration/rack_spec.rb
164
+ - spec/integration/redirect_spec.rb
165
+ - spec/spec_helper.rb
166
+ - spec/support/assertions.rb
167
+ - spec/unit/builder_spec.rb
168
+ - spec/unit/route_spec.rb
169
+ - spec/unit/routes_spec.rb
170
+ - spec/unit/rutter_spec.rb
171
+ - spec/unit/scope_spec.rb