wayfarer 0.4.6 → 0.4.8

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.
Files changed (259) hide show
  1. checksums.yaml +4 -4
  2. data/.env +17 -0
  3. data/.github/workflows/lint.yaml +27 -0
  4. data/.github/workflows/release.yaml +30 -0
  5. data/.github/workflows/tests.yaml +21 -0
  6. data/.gitignore +5 -1
  7. data/.rubocop.yml +36 -0
  8. data/.vale.ini +8 -0
  9. data/.yardopts +1 -3
  10. data/Dockerfile +6 -4
  11. data/Gemfile +24 -0
  12. data/Gemfile.lock +274 -164
  13. data/Rakefile +7 -51
  14. data/bin/wayfarer +1 -1
  15. data/docker-compose.yml +23 -13
  16. data/docs/cookbook/consent_screen.md +2 -2
  17. data/docs/cookbook/executing_javascript.md +3 -3
  18. data/docs/cookbook/navigation.md +12 -12
  19. data/docs/cookbook/querying_html.md +3 -3
  20. data/docs/cookbook/screenshots.md +2 -2
  21. data/docs/guides/callbacks.md +25 -125
  22. data/docs/guides/cli.md +71 -0
  23. data/docs/guides/configuration.md +10 -35
  24. data/docs/guides/development.md +67 -0
  25. data/docs/guides/handlers.md +60 -0
  26. data/docs/guides/index.md +1 -0
  27. data/docs/guides/jobs.md +142 -31
  28. data/docs/guides/navigation.md +1 -1
  29. data/docs/guides/networking/capybara.md +13 -22
  30. data/docs/guides/networking/custom_adapters.md +103 -41
  31. data/docs/guides/networking/ferrum.md +4 -4
  32. data/docs/guides/networking/http.md +9 -13
  33. data/docs/guides/networking/selenium.md +10 -11
  34. data/docs/guides/pages.md +78 -10
  35. data/docs/guides/redis.md +10 -0
  36. data/docs/guides/routing.md +156 -0
  37. data/docs/guides/tasks.md +53 -9
  38. data/docs/guides/tutorial.md +66 -0
  39. data/docs/guides/user_agents.md +115 -0
  40. data/docs/index.md +17 -40
  41. data/lib/wayfarer/base.rb +125 -46
  42. data/lib/wayfarer/batch_completion.rb +60 -0
  43. data/lib/wayfarer/callbacks.rb +22 -48
  44. data/lib/wayfarer/cli/route_printer.rb +85 -89
  45. data/lib/wayfarer/cli.rb +103 -0
  46. data/lib/wayfarer/gc.rb +18 -6
  47. data/lib/wayfarer/handler.rb +15 -7
  48. data/lib/wayfarer/kv.rb +28 -0
  49. data/lib/wayfarer/logging.rb +38 -0
  50. data/lib/wayfarer/middleware/base.rb +2 -0
  51. data/lib/wayfarer/middleware/batch_completion.rb +19 -0
  52. data/lib/wayfarer/middleware/chain.rb +7 -1
  53. data/lib/wayfarer/middleware/content_type.rb +59 -0
  54. data/lib/wayfarer/middleware/controller.rb +19 -15
  55. data/lib/wayfarer/middleware/dedup.rb +22 -13
  56. data/lib/wayfarer/middleware/dispatch.rb +17 -4
  57. data/lib/wayfarer/middleware/normalize.rb +7 -14
  58. data/lib/wayfarer/middleware/redis.rb +15 -0
  59. data/lib/wayfarer/middleware/router.rb +33 -35
  60. data/lib/wayfarer/middleware/stage.rb +5 -5
  61. data/lib/wayfarer/middleware/uri_parser.rb +31 -0
  62. data/lib/wayfarer/middleware/user_agent.rb +49 -0
  63. data/lib/wayfarer/networking/capybara.rb +1 -1
  64. data/lib/wayfarer/networking/context.rb +14 -3
  65. data/lib/wayfarer/networking/ferrum.rb +1 -4
  66. data/lib/wayfarer/networking/follow.rb +14 -7
  67. data/lib/wayfarer/networking/http.rb +1 -1
  68. data/lib/wayfarer/networking/pool.rb +23 -13
  69. data/lib/wayfarer/networking/selenium.rb +15 -7
  70. data/lib/wayfarer/networking/strategy.rb +2 -2
  71. data/lib/wayfarer/page.rb +34 -14
  72. data/lib/wayfarer/parsing/xml.rb +6 -6
  73. data/lib/wayfarer/parsing.rb +21 -0
  74. data/lib/wayfarer/redis/barrier.rb +26 -21
  75. data/lib/wayfarer/redis/counter.rb +18 -9
  76. data/lib/wayfarer/redis/pool.rb +1 -1
  77. data/lib/wayfarer/redis/resettable.rb +19 -0
  78. data/lib/wayfarer/routing/dsl.rb +166 -30
  79. data/lib/wayfarer/routing/hash_stack.rb +33 -0
  80. data/lib/wayfarer/routing/matchers/custom.rb +8 -5
  81. data/lib/wayfarer/routing/matchers/{suffix.rb → empty_params.rb} +2 -6
  82. data/lib/wayfarer/routing/matchers/host.rb +15 -9
  83. data/lib/wayfarer/routing/matchers/path.rb +11 -31
  84. data/lib/wayfarer/routing/matchers/query.rb +41 -17
  85. data/lib/wayfarer/routing/matchers/result.rb +12 -0
  86. data/lib/wayfarer/routing/matchers/scheme.rb +13 -5
  87. data/lib/wayfarer/routing/matchers/url.rb +13 -5
  88. data/lib/wayfarer/routing/path_consumer.rb +130 -0
  89. data/lib/wayfarer/routing/path_finder.rb +151 -23
  90. data/lib/wayfarer/routing/result.rb +1 -1
  91. data/lib/wayfarer/routing/root_route.rb +17 -1
  92. data/lib/wayfarer/routing/route.rb +66 -19
  93. data/lib/wayfarer/routing/serializable.rb +28 -0
  94. data/lib/wayfarer/routing/sub_route.rb +53 -0
  95. data/lib/wayfarer/routing/target_route.rb +17 -1
  96. data/lib/wayfarer/stringify.rb +21 -30
  97. data/lib/wayfarer/task.rb +9 -17
  98. data/lib/wayfarer/uri/normalization.rb +120 -0
  99. data/lib/wayfarer.rb +72 -5
  100. data/mise.toml +2 -0
  101. data/mkdocs.yml +44 -8
  102. data/rake/docs.rake +26 -0
  103. data/rake/lint.rake +9 -0
  104. data/rake/release.rake +23 -0
  105. data/rake/tests.rake +32 -0
  106. data/requirements.txt +1 -1
  107. data/spec/factories/job.rb +8 -0
  108. data/spec/factories/middleware.rb +2 -2
  109. data/spec/factories/path_finder.rb +11 -0
  110. data/spec/factories/redis.rb +19 -0
  111. data/spec/factories/task.rb +46 -2
  112. data/spec/spec_helpers.rb +55 -51
  113. data/spec/support/active_job_helpers.rb +8 -0
  114. data/spec/support/integration_helpers.rb +21 -0
  115. data/spec/support/redis_helpers.rb +9 -0
  116. data/spec/support/test_app.rb +66 -37
  117. data/spec/wayfarer/base_spec.rb +200 -0
  118. data/spec/wayfarer/batch_completion_spec.rb +142 -0
  119. data/spec/wayfarer/cli/job_spec.rb +88 -0
  120. data/spec/wayfarer/cli/routing_spec.rb +322 -0
  121. data/spec/{cli → wayfarer/cli}/version_spec.rb +1 -1
  122. data/spec/wayfarer/gc_spec.rb +29 -0
  123. data/spec/wayfarer/handler_spec.rb +9 -0
  124. data/spec/wayfarer/integration/callbacks_spec.rb +200 -0
  125. data/spec/wayfarer/integration/content_type_spec.rb +37 -0
  126. data/spec/wayfarer/integration/custom_routing_spec.rb +51 -0
  127. data/spec/wayfarer/integration/gc_spec.rb +40 -0
  128. data/spec/wayfarer/integration/handler_spec.rb +65 -0
  129. data/spec/wayfarer/integration/page_spec.rb +79 -0
  130. data/spec/wayfarer/integration/params_spec.rb +64 -0
  131. data/spec/wayfarer/integration/parsing_spec.rb +99 -0
  132. data/spec/wayfarer/integration/retry_spec.rb +112 -0
  133. data/spec/wayfarer/integration/stage_spec.rb +58 -0
  134. data/spec/wayfarer/middleware/batch_completion_spec.rb +33 -0
  135. data/spec/{middleware → wayfarer/middleware}/chain_spec.rb +24 -19
  136. data/spec/wayfarer/middleware/content_type_spec.rb +83 -0
  137. data/spec/{middleware → wayfarer/middleware}/controller_spec.rb +24 -22
  138. data/spec/wayfarer/middleware/dedup_spec.rb +66 -0
  139. data/spec/wayfarer/middleware/normalize_spec.rb +32 -0
  140. data/spec/wayfarer/middleware/router_spec.rb +102 -0
  141. data/spec/wayfarer/middleware/stage_spec.rb +63 -0
  142. data/spec/wayfarer/middleware/uri_parser_spec.rb +63 -0
  143. data/spec/wayfarer/middleware/user_agent_spec.rb +158 -0
  144. data/spec/wayfarer/networking/capybara_spec.rb +13 -0
  145. data/spec/{networking → wayfarer/networking}/context_spec.rb +46 -38
  146. data/spec/wayfarer/networking/ferrum_spec.rb +13 -0
  147. data/spec/{networking → wayfarer/networking}/follow_spec.rb +11 -6
  148. data/spec/wayfarer/networking/http_spec.rb +12 -0
  149. data/spec/{networking → wayfarer/networking}/pool_spec.rb +16 -14
  150. data/spec/wayfarer/networking/selenium_spec.rb +12 -0
  151. data/spec/{networking → wayfarer/networking}/strategy.rb +33 -54
  152. data/spec/wayfarer/page_spec.rb +69 -0
  153. data/spec/{parsing → wayfarer/parsing}/json_spec.rb +1 -1
  154. data/spec/wayfarer/parsing/xml_parse_spec.rb +25 -0
  155. data/spec/wayfarer/redis/barrier_spec.rb +39 -0
  156. data/spec/wayfarer/redis/counter_spec.rb +34 -0
  157. data/spec/{redis → wayfarer/redis}/pool_spec.rb +4 -3
  158. data/spec/{routing → wayfarer/routing}/dsl_spec.rb +12 -22
  159. data/spec/wayfarer/routing/hash_stack_spec.rb +63 -0
  160. data/spec/wayfarer/routing/integration_spec.rb +101 -0
  161. data/spec/wayfarer/routing/matchers/custom_spec.rb +39 -0
  162. data/spec/wayfarer/routing/matchers/host_spec.rb +56 -0
  163. data/spec/wayfarer/routing/matchers/matcher.rb +17 -0
  164. data/spec/wayfarer/routing/matchers/path_spec.rb +43 -0
  165. data/spec/wayfarer/routing/matchers/query_spec.rb +123 -0
  166. data/spec/wayfarer/routing/matchers/scheme_spec.rb +45 -0
  167. data/spec/wayfarer/routing/matchers/url_spec.rb +33 -0
  168. data/spec/wayfarer/routing/path_consumer_spec.rb +123 -0
  169. data/spec/wayfarer/routing/path_finder_spec.rb +409 -0
  170. data/spec/wayfarer/routing/root_route_spec.rb +51 -0
  171. data/spec/wayfarer/routing/route_spec.rb +74 -0
  172. data/spec/wayfarer/routing/sub_route_spec.rb +103 -0
  173. data/spec/wayfarer/task_spec.rb +13 -0
  174. data/spec/wayfarer/uri/normalization_spec.rb +98 -0
  175. data/spec/wayfarer_spec.rb +2 -2
  176. data/wayfarer.gemspec +18 -28
  177. metadata +797 -265
  178. data/.github/workflows/ci.yaml +0 -32
  179. data/.rbenv-gemsets +0 -1
  180. data/.ruby-version +0 -1
  181. data/RELEASING.md +0 -17
  182. data/docs/cookbook/user_agent.md +0 -7
  183. data/docs/guides/error_handling.md +0 -53
  184. data/docs/guides/networking.md +0 -94
  185. data/docs/guides/performance.md +0 -130
  186. data/docs/guides/reliability.md +0 -41
  187. data/docs/guides/routing/steering.md +0 -30
  188. data/docs/reference/api/base.md +0 -48
  189. data/docs/reference/cli.md +0 -61
  190. data/docs/reference/configuration_keys.md +0 -43
  191. data/docs/reference/environment_variables.md +0 -83
  192. data/lib/wayfarer/cli/base.rb +0 -45
  193. data/lib/wayfarer/cli/generate.rb +0 -17
  194. data/lib/wayfarer/cli/job.rb +0 -56
  195. data/lib/wayfarer/cli/route.rb +0 -29
  196. data/lib/wayfarer/cli/runner.rb +0 -34
  197. data/lib/wayfarer/cli/templates/Gemfile.tt +0 -5
  198. data/lib/wayfarer/cli/templates/job.rb.tt +0 -10
  199. data/lib/wayfarer/config/capybara.rb +0 -10
  200. data/lib/wayfarer/config/ferrum.rb +0 -11
  201. data/lib/wayfarer/config/networking.rb +0 -29
  202. data/lib/wayfarer/config/redis.rb +0 -14
  203. data/lib/wayfarer/config/root.rb +0 -11
  204. data/lib/wayfarer/config/selenium.rb +0 -21
  205. data/lib/wayfarer/config/strconv.rb +0 -45
  206. data/lib/wayfarer/config/struct.rb +0 -72
  207. data/lib/wayfarer/middleware/fetch.rb +0 -56
  208. data/lib/wayfarer/redis/connection.rb +0 -13
  209. data/lib/wayfarer/redis/version.rb +0 -19
  210. data/lib/wayfarer/routing/router.rb +0 -28
  211. data/spec/base_spec.rb +0 -224
  212. data/spec/callbacks_spec.rb +0 -102
  213. data/spec/cli/generate_spec.rb +0 -39
  214. data/spec/cli/job_spec.rb +0 -78
  215. data/spec/config/capybara_spec.rb +0 -18
  216. data/spec/config/ferrum_spec.rb +0 -24
  217. data/spec/config/networking_spec.rb +0 -73
  218. data/spec/config/redis_spec.rb +0 -32
  219. data/spec/config/root_spec.rb +0 -31
  220. data/spec/config/selenium_spec.rb +0 -56
  221. data/spec/config/strconv_spec.rb +0 -58
  222. data/spec/config/struct_spec.rb +0 -66
  223. data/spec/fixtures/dummy_job.rb +0 -7
  224. data/spec/gc_spec.rb +0 -59
  225. data/spec/handler_spec.rb +0 -11
  226. data/spec/integration/callbacks_spec.rb +0 -85
  227. data/spec/integration/page_spec.rb +0 -62
  228. data/spec/integration/params_spec.rb +0 -56
  229. data/spec/integration/stage_spec.rb +0 -51
  230. data/spec/integration/steering_spec.rb +0 -57
  231. data/spec/middleware/dedup_spec.rb +0 -88
  232. data/spec/middleware/dispatch_spec.rb +0 -43
  233. data/spec/middleware/fetch_spec.rb +0 -155
  234. data/spec/middleware/normalize_spec.rb +0 -29
  235. data/spec/middleware/router_spec.rb +0 -105
  236. data/spec/middleware/stage_spec.rb +0 -62
  237. data/spec/networking/capybara_spec.rb +0 -12
  238. data/spec/networking/ferrum_spec.rb +0 -12
  239. data/spec/networking/http_spec.rb +0 -12
  240. data/spec/networking/selenium_spec.rb +0 -12
  241. data/spec/page_spec.rb +0 -47
  242. data/spec/parsing/xml_spec.rb +0 -25
  243. data/spec/redis/barrier_spec.rb +0 -78
  244. data/spec/redis/counter_spec.rb +0 -32
  245. data/spec/redis/version_spec.rb +0 -13
  246. data/spec/routing/integration_spec.rb +0 -110
  247. data/spec/routing/matchers/custom_spec.rb +0 -31
  248. data/spec/routing/matchers/host_spec.rb +0 -49
  249. data/spec/routing/matchers/path_spec.rb +0 -43
  250. data/spec/routing/matchers/query_spec.rb +0 -137
  251. data/spec/routing/matchers/scheme_spec.rb +0 -25
  252. data/spec/routing/matchers/suffix_spec.rb +0 -41
  253. data/spec/routing/matchers/uri_spec.rb +0 -27
  254. data/spec/routing/path_finder_spec.rb +0 -33
  255. data/spec/routing/root_route_spec.rb +0 -29
  256. data/spec/routing/route_spec.rb +0 -43
  257. data/spec/routing/router_spec.rb +0 -24
  258. data/spec/task_spec.rb +0 -34
  259. data/spec/{stringify_spec.rb → wayfarer/stringify_spec.rb} +2 -2
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ RSpec.describe Wayfarer::Routing::PathConsumer do
6
+ subject(:consumer) { described_class.new(input_path) }
7
+
8
+ let(:input_path) { "users/42" }
9
+
10
+ let(:dummy_matcher_class) do
11
+ Class.new(Wayfarer::Routing::Matchers::Path) do
12
+ attr_reader :pattern
13
+
14
+ def initialize(pattern) # rubocop:disable Lint/MissingSuper
15
+ @pattern = pattern
16
+ end
17
+ end
18
+ end
19
+
20
+ let(:match_data) do
21
+ instance_double(MatchData, to_s: "/users/42", named_captures: { "id" => "42" })
22
+ end
23
+
24
+ let(:pattern_double) do
25
+ instance_double(Mustermann::AST::Pattern, peek_match: match_data)
26
+ end
27
+
28
+ let(:matcher) { dummy_matcher_class.new(pattern_double) }
29
+
30
+ let(:fake_route) { instance_double(Wayfarer::Routing::Route, matcher: matcher) }
31
+
32
+ describe "initial state" do
33
+ it { is_expected.to have_attributes(path: "/users/42") }
34
+
35
+ it "starts with one consumption state" do
36
+ expect(consumer.send(:states).size).to eq 1
37
+ end
38
+
39
+ it "has no offset and is valid", :aggregate_failures do
40
+ state = consumer.send(:current_state)
41
+ expect(state.offset).to be_nil
42
+ expect(state.params).to eq({})
43
+ expect(state.valid?).to be true
44
+ end
45
+ end
46
+
47
+ describe "#valid?" do
48
+ context "when nothing is consumed" do
49
+ it { is_expected.to be_valid }
50
+ end
51
+
52
+ context "when fully consumed" do
53
+ before { consumer.push(fake_route) }
54
+
55
+ it { is_expected.to be_valid }
56
+ end
57
+
58
+ context "when partially consumed and not at start" do
59
+ before do
60
+ allow(match_data).to receive_messages(to_s: "/users", named_captures: {})
61
+ consumer.push(fake_route)
62
+ end
63
+
64
+ it { is_expected.not_to be_valid }
65
+ end
66
+
67
+ context "when current state is invalid" do
68
+ before do
69
+ allow(pattern_double).to receive(:peek_match).and_return(nil)
70
+ consumer.push(fake_route)
71
+ end
72
+
73
+ it { is_expected.not_to be_valid }
74
+ end
75
+ end
76
+
77
+ describe "#push" do
78
+ context "when route matcher is not a Path" do
79
+ let(:matcher) { Object.new }
80
+
81
+ it "does not change offset or validity" do
82
+ expect {
83
+ consumer.push(instance_double(Wayfarer::Routing::Route, matcher: matcher))
84
+ }.not_to(change { consumer.send(:current_state).offset })
85
+
86
+ expect(consumer).to be_valid
87
+ end
88
+ end
89
+
90
+ context "when route matcher is a Path and the sub_path matches" do
91
+ before { consumer.push(fake_route) }
92
+
93
+ it "updates offset and merges captured params" do
94
+ current = consumer.send(:current_state)
95
+ expect(current.offset).to eq("/users/42".length)
96
+ expect(current.params).to eq("id" => "42")
97
+ expect(current.valid?).to be true
98
+ end
99
+ end
100
+
101
+ context "when route matcher is a Path but it fails to match" do
102
+ before do
103
+ allow(pattern_double).to receive(:peek_match).and_return(nil)
104
+ consumer.push(fake_route)
105
+ end
106
+
107
+ it "invalidates the new state" do
108
+ expect(consumer.send(:current_state).valid?).to be false
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "#pop" do
114
+ before { consumer.push(fake_route) }
115
+
116
+ it "reverts to the previous state" do
117
+ expect {
118
+ consumer.pop
119
+ }.to change { consumer.send(:states).size }.by(-1)
120
+ expect(consumer.send(:current_state).offset).to be_nil
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,409 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ describe Wayfarer::Routing::PathFinder do
6
+ subject(:result) { described_class.result(route, task) }
7
+
8
+ let(:route) { Wayfarer::Routing::RootRoute.new }
9
+ let(:task) { build(:task, :uri, url: input_url) }
10
+
11
+ shared_examples "matches" do |url, action, params = Wayfarer::Routing::Route::EMPTY_PARAMS|
12
+ let(:input_url) { url }
13
+
14
+ specify do
15
+ expect(result).to be_a(Wayfarer::Routing::Result::Match)
16
+ expect(result.action).to eq(action)
17
+ expect(result.params).to eq(params)
18
+ end
19
+ end
20
+
21
+ shared_examples "mismatches" do |url|
22
+ let(:input_url) { url }
23
+
24
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Mismatch) }
25
+ end
26
+
27
+ context "without child routes" do
28
+ it_behaves_like "mismatches", "https://example.com/foo"
29
+ it_behaves_like "mismatches", "http://w3c.org#foobar"
30
+ it_behaves_like "mismatches", "localhost"
31
+ it_behaves_like "mismatches", "https://example.com:8080/foo"
32
+ it_behaves_like "mismatches", "http://127.0.0.1/foo"
33
+ it_behaves_like "mismatches", "./foo"
34
+ end
35
+
36
+ context "with single root action" do
37
+ before { route.to(:bar) }
38
+
39
+ it_behaves_like "matches", "https://example.com", :bar
40
+ it_behaves_like "matches", "https://example.com/foo", :bar
41
+ it_behaves_like "matches", "http://localhost?foo=bar", :bar
42
+ it_behaves_like "matches", "http://localhost#foobar", :bar
43
+ it_behaves_like "matches", "https://example.com:3000?test=123", :bar
44
+ end
45
+
46
+ context "with multiple root actions" do
47
+ before { route.to(:alpha).to(:beta) }
48
+
49
+ it_behaves_like "matches", "https://example.com", :beta
50
+ it_behaves_like "matches", "https://example.com/foo", :beta
51
+ it_behaves_like "matches", "http://localhost?foo=bar", :beta
52
+ it_behaves_like "matches", "http://localhost#foobar", :beta
53
+ it_behaves_like "matches", "https://example.com/#top", :beta
54
+ end
55
+
56
+ context "with literal root path with root action" do
57
+ before { route.path("/").to(:root) }
58
+
59
+ it_behaves_like "matches", "https://example.com", :root
60
+ it_behaves_like "matches", "https://example.com/", :root
61
+ it_behaves_like "matches", "https://example.com:8000", :root
62
+ it_behaves_like "matches", "https://example.com:8000/", :root
63
+ it_behaves_like "matches", "http://localhost?foo=bar", :root
64
+ it_behaves_like "matches", "http://localhost#foobar", :root
65
+ it_behaves_like "matches", "https://example.com//", :root
66
+
67
+ it_behaves_like "mismatches", "https://example.com/test"
68
+ it_behaves_like "mismatches", "https://w3c.org/foo/bar"
69
+ end
70
+
71
+ context "with path segments" do
72
+ before { route.path("/").path(":segment").path("/").to(:root) }
73
+
74
+ it_behaves_like "matches", "https://example.com/test", :root, { "segment" => "test" }
75
+ it_behaves_like "matches", "https://example.com/test/", :root, { "segment" => "test" }
76
+ it_behaves_like "matches", "http://localhost/test?foo=bar", :root, { "segment" => "test" }
77
+ it_behaves_like "matches", "http://localhost/test/#foobar", :root, { "segment" => "test" }
78
+ it_behaves_like "matches", "https://example.com/%20/", :root, { "segment" => "%20" }
79
+
80
+ it_behaves_like "mismatches", "https://example.com/foo/bar"
81
+ it_behaves_like "mismatches", "http://localhost:8000/foo/bar"
82
+ end
83
+
84
+ context "with multiple child rules" do
85
+ before do
86
+ route.path("foo").to(:foo)
87
+ route.path("bar").to(:bar)
88
+ end
89
+
90
+ it_behaves_like "matches", "http://0.0.0.0/foo", :foo
91
+ it_behaves_like "matches", "http://0.0.0.0/bar", :bar
92
+ it_behaves_like "matches", "http://0.0.0.0/foo/", :foo
93
+
94
+ it_behaves_like "mismatches", "http://0.0.0.0/qux"
95
+ end
96
+
97
+ context "when matching a specific URL exactly" do
98
+ before { route.url("https://example.com").to(:index) }
99
+
100
+ it_behaves_like "matches", "https://example.com", :index
101
+ it_behaves_like "matches", "https://example.com/", :index
102
+
103
+ it_behaves_like "mismatches", "http://example.com"
104
+ it_behaves_like "mismatches", "https://example.com?foo=bar"
105
+ it_behaves_like "mismatches", "https://example.com#fragmen"
106
+ it_behaves_like "mismatches", "https://example.com:8080"
107
+ end
108
+
109
+ context "with query parameters" do
110
+ before { route.query({ foo: "bar" }).to(:index) }
111
+
112
+ it_behaves_like "matches", "https://example.com?foo=bar", :index, { "foo" => "bar" }
113
+ it_behaves_like "matches", "https://example.com/test?bar=qux&foo=bar", :index, { "foo" => "bar" }
114
+ it_behaves_like "matches", "https://example.com?foo=bar&extra=1", :index, { "foo" => "bar" }
115
+
116
+ it_behaves_like "mismatches", "https://example.com"
117
+ it_behaves_like "mismatches", "https://example.com?foo=qux"
118
+ end
119
+
120
+ context "with multiple query parameters" do
121
+ before { route.to(:index).query({ foo: "bar" }).query({ page: 10 }) }
122
+
123
+ it_behaves_like "matches", "https://example.com?foo=bar&page=10", :index, { "foo" => "bar", "page" => "10" }
124
+ it_behaves_like "matches",
125
+ "https://example.com/test?bar=qux&page=10&foo=bar",
126
+ :index,
127
+ { "foo" => "bar", "page" => "10" }
128
+ it_behaves_like "matches",
129
+ "https://example.com?foo=bar&foo=bar&page=10",
130
+ :index,
131
+ { "foo" => "bar", "page" => "10" }
132
+
133
+ it_behaves_like "mismatches", "https://example.com?foo=qux"
134
+ it_behaves_like "mismatches", "https://example.com?page=10"
135
+ it_behaves_like "mismatches", "https://example.com?page=10&foo=qux"
136
+ end
137
+
138
+ context "with regular expression query parameter constraint" do
139
+ before { route.to(:alpha).query({ foo: /ooba/, page: 5..12 }).to(:beta) }
140
+
141
+ it_behaves_like "matches",
142
+ "https://example.com?foo=foobar&page=9",
143
+ :beta,
144
+ { "foo" => "foobar", "page" => "9" }
145
+ it_behaves_like "matches",
146
+ "https://example.com?foo=foobar&page=5",
147
+ :beta,
148
+ { "foo" => "foobar", "page" => "5" }
149
+ it_behaves_like "matches",
150
+ "https://example.com?foo=foobar&page=12",
151
+ :beta,
152
+ { "foo" => "foobar", "page" => "12" }
153
+
154
+ it_behaves_like "mismatches", "https://example.com?foo=foobar&page=ten"
155
+ it_behaves_like "mismatches", "https://example.com?foo=foobar&page=4"
156
+ it_behaves_like "mismatches", "https://example.com?foo=foobar&page=13"
157
+ it_behaves_like "mismatches", "https://example.com?foobar=qux"
158
+ it_behaves_like "mismatches", "https://example.com?page=10"
159
+ it_behaves_like "mismatches", "https://example.com?page=10&foo=qux"
160
+ end
161
+
162
+ context "with scheme" do
163
+ before do
164
+ route.scheme(:https).to(:foo)
165
+ route.scheme(:http).to(:bar)
166
+ end
167
+
168
+ it_behaves_like "matches", "https://example.com", :foo
169
+ it_behaves_like "matches", "http://w3c.org", :bar
170
+ it_behaves_like "matches", "https://example.com/test", :foo
171
+
172
+ it_behaves_like "mismatches", "ftp://example.com"
173
+ # TODO: Route normalized URL
174
+ it_behaves_like "mismatches", "HTTP://w3c.org"
175
+ end
176
+
177
+ context "with custom route and potentially `nil` action" do
178
+ before do
179
+ route.host("example.com").custom do |root, uri|
180
+ root.to(:index) if uri.path == "/foo/bar"
181
+ end
182
+ end
183
+
184
+ it_behaves_like "matches", "https://example.com/foo/bar", :index
185
+
186
+ it_behaves_like "mismatches", "https://example.com"
187
+ it_behaves_like "mismatches", "https://example.com/foo/bar/"
188
+ end
189
+
190
+ context "with custom route and path segments" do
191
+ before do
192
+ route.host("example.com").custom do |root, _uri|
193
+ root.path(":alpha/:beta").to(:index)
194
+ end
195
+ end
196
+
197
+ it_behaves_like "matches", "https://example.com/foo/bar", :index, { "alpha" => "foo", "beta" => "bar" }
198
+
199
+ it_behaves_like "mismatches", "https://example.com"
200
+ it_behaves_like "mismatches", "https://example.com/foo/bar/baz"
201
+ end
202
+
203
+ context "with overriding action from custom route" do
204
+ before do
205
+ route.host("example.com").path(":foo").to(:alpha).custom do |root, _uri, _task|
206
+ root.path(":bar").to(:beta)
207
+ end
208
+ end
209
+
210
+ it_behaves_like "matches",
211
+ "https://example.com/alpha/beta",
212
+ :beta,
213
+ { "foo" => "alpha", "bar" => "beta" }
214
+
215
+ it_behaves_like "mismatches", "https://example.com"
216
+ it_behaves_like "mismatches", "https://example.com/alpha/"
217
+ end
218
+
219
+ context "with regular expression host and multiple child rules" do
220
+ before do
221
+ route.host(/example.com/) do
222
+ path "users/:id", query: { display: "gallery" }, to: :gallery
223
+ path "users/:id", to: :user
224
+ end
225
+ end
226
+
227
+ it_behaves_like "matches", "https://example.com/users/123", :user, { "id" => "123" }
228
+ it_behaves_like "matches",
229
+ "https://example.com/users/123?display=gallery",
230
+ :gallery,
231
+ { "id" => "123", "display" => "gallery" }
232
+ it_behaves_like "matches", "https://sub.example.com/users/456", :user, { "id" => "456" }
233
+
234
+ it_behaves_like "mismatches", "https://example.com"
235
+ it_behaves_like "mismatches", "https://example.com/users"
236
+ it_behaves_like "mismatches", "https://example.com/users/"
237
+ it_behaves_like "mismatches", "https://example.co/users/123"
238
+ end
239
+
240
+ context "with protocol-relative URL" do
241
+ before { route.host("example.com").to(:index) }
242
+
243
+ it_behaves_like "mismatches", "//example.com"
244
+ it_behaves_like "mismatches", "//example.com/foo"
245
+ it_behaves_like "mismatches", "//example.com:3000/subdir"
246
+ it_behaves_like "mismatches", "/only-path"
247
+ end
248
+
249
+ context "with duplicate query parameters" do
250
+ before { route.query({ foo: "bar" }).to(:index) }
251
+
252
+ it_behaves_like "matches", "https://example.com?foo=bar", :index, { "foo" => "bar" }
253
+ it_behaves_like "matches", "https://example.com?foo=bar&foo=baz", :index, { "foo" => "baz" }
254
+ it_behaves_like "matches", "https://example.com?foo=bar&foo=bar", :index, { "foo" => "bar" }
255
+ it_behaves_like "matches", "https://example.com?foo=bar&foo=", :index, { "foo" => "" }
256
+
257
+ it_behaves_like "mismatches", "https://example.com?foo="
258
+ end
259
+
260
+ context "with overlapping routes" do
261
+ before do
262
+ route.path("users").to(:users_index)
263
+ route.path("users").path(":id").to(:user_show)
264
+ end
265
+
266
+ it_behaves_like "matches", "https://example.com/users", :users_index
267
+ it_behaves_like "matches", "https://example.com/users/", :users_index
268
+ it_behaves_like "matches", "https://example.com/users?foo=bar", :users_index
269
+ it_behaves_like "matches", "https://example.com/users/123", :user_show, { "id" => "123" }
270
+ it_behaves_like "matches", "https://example.com/users?foo=bar&bar=baz", :users_index
271
+ it_behaves_like "matches", "https://example.com/users/456?foo=bar", :user_show, { "id" => "456" }
272
+
273
+ it_behaves_like "mismatches", "https://example.com"
274
+ it_behaves_like "mismatches", "https://example.com/"
275
+ it_behaves_like "mismatches", "https://example.com/users/123/extra"
276
+ it_behaves_like "mismatches", "https://example.com/users/123/extra/"
277
+ end
278
+
279
+ context "with file scheme" do
280
+ before { route.scheme("file").path("home").to(:local_file) }
281
+
282
+ it_behaves_like "matches", "file:///home", :local_file
283
+ it_behaves_like "matches", "file:///home/", :local_file
284
+
285
+ it_behaves_like "mismatches", "https:///home"
286
+ it_behaves_like "mismatches", "file:///Users"
287
+ it_behaves_like "mismatches", "file://home"
288
+ end
289
+
290
+ context "without URL scheme" do
291
+ before { route.path("localhost").to(:foo) }
292
+
293
+ it_behaves_like "mismatches", "localhost", :foo, {}
294
+ it_behaves_like "mismatches", "/localhost", :foo, {}
295
+ it_behaves_like "mismatches", "/localhost/", :foo, {}
296
+ it_behaves_like "mismatches", "//localhost", :foo, {}
297
+ end
298
+
299
+ context "with host" do
300
+ before { route.host("localhost").to(:foo) }
301
+
302
+ it_behaves_like "matches", "http://localhost", :foo
303
+ it_behaves_like "matches", "ftp://localhost/foo#bar", :foo
304
+ it_behaves_like "matches", "http://localhost:8080", :foo
305
+
306
+ it_behaves_like "mismatches", "https://127.0.0.1"
307
+ it_behaves_like "mismatches", "localhost:8000"
308
+ it_behaves_like "mismatches", "localhost"
309
+ it_behaves_like "mismatches", "/localhost"
310
+ it_behaves_like "mismatches", "/localhost/"
311
+ end
312
+
313
+ context "with multiple rules" do
314
+ before do
315
+ route.host(/example.com/, path: "/foo") do
316
+ path "/", to: :foo
317
+ path ":segment", to: :bar
318
+ end
319
+ end
320
+
321
+ it_behaves_like "matches", "ftp://sub.example.com/foo", :foo
322
+ it_behaves_like "matches", "ftp://sub.example.com/foo/qux", :bar, { "segment" => "qux" }
323
+
324
+ it_behaves_like "mismatches", "sub.example.com/foo"
325
+ it_behaves_like "mismatches", "sub.example.com/foo/qux"
326
+ end
327
+
328
+ describe "inline declaration" do
329
+ before do
330
+ route.path("/").path("/foo").path(":segment").path("/qux").to(:foo)
331
+ end
332
+
333
+ it_behaves_like "matches", "http://example.com/foo/bar/qux", :foo, "segment" => "bar"
334
+ it_behaves_like "matches", "http://example.com/foo/bar/qux/", :foo, "segment" => "bar"
335
+ it_behaves_like "matches", "http://example.com/foo/bar/qux#fragment", :foo, "segment" => "bar"
336
+ it_behaves_like "matches", "http://example.com//foobar//qux", :foo, "segment" => "bar"
337
+
338
+ it_behaves_like "mismatches", "http://example.com"
339
+ it_behaves_like "mismatches", "http://example.com/foo"
340
+ it_behaves_like "mismatches", "http://example.com/foo/qux"
341
+ it_behaves_like "mismatches", "http://sub.example.com/foo/qux"
342
+ end
343
+
344
+ describe "callback" do
345
+ subject(:result) { described_class.result(route, task, &callback) }
346
+
347
+ let(:calls) { [] }
348
+ let(:callback) do
349
+ lambda do |route, result, finder|
350
+ calls.push(route: route,
351
+ matcher: route.matcher,
352
+ result: result,
353
+ params: finder.params_stack.to_h)
354
+ end
355
+ end
356
+
357
+ let(:route) do
358
+ Wayfarer::Routing::RootRoute.new.tap do |root|
359
+ root.host "example.com", scheme: :https do
360
+ path "contact", to: :contact
361
+ path "users/:id" do
362
+ to :foo
363
+
364
+ path "gallery", to: :bar
365
+ end
366
+ end
367
+ end
368
+ end
369
+
370
+ let(:task) { build(:task, :uri, url: url) }
371
+
372
+ context "with matching URL" do
373
+ let(:url) { "https://example.com/users/42" }
374
+
375
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Match) }
376
+
377
+ it "invokes the callback during route matching" do
378
+ result
379
+
380
+ expect(calls).to contain_exactly(
381
+ a_hash_including(route: instance_of(Wayfarer::Routing::RootRoute)),
382
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Host)),
383
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Scheme)),
384
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Path), result: false),
385
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Path), result: true),
386
+ a_hash_including(route: instance_of(Wayfarer::Routing::TargetRoute))
387
+ )
388
+ end
389
+ end
390
+
391
+ context "with mismatching URL" do
392
+ let(:url) { "https://example.com/foo" }
393
+
394
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Mismatch) }
395
+
396
+ it "invokes the callback during route matching" do
397
+ result
398
+
399
+ expect(calls).to contain_exactly(
400
+ a_hash_including(route: instance_of(Wayfarer::Routing::RootRoute)),
401
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Host)),
402
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Scheme)),
403
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Path), result: false),
404
+ a_hash_including(matcher: instance_of(Wayfarer::Routing::Matchers::Path), result: false)
405
+ )
406
+ end
407
+ end
408
+ end
409
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ describe Wayfarer::Routing::RootRoute do
6
+ subject(:result) { route.invoke(task) }
7
+
8
+ let(:task) { build(:task, :uri, url: url) }
9
+
10
+ describe "#invoke" do
11
+ let(:route) do
12
+ described_class.new.tap do |root|
13
+ root.host test_app_hostname do
14
+ path "test", to: :index
15
+ end
16
+ end
17
+ end
18
+
19
+ context "with matching URL" do
20
+ let(:url) { test_app_path("test") }
21
+
22
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Match) }
23
+ end
24
+
25
+ context "with mismatching URL" do
26
+ let(:url) { test_app_path("mismatch") }
27
+
28
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Mismatch) }
29
+ end
30
+ end
31
+
32
+ describe "#evaluate" do
33
+ subject(:result) { route.evaluate(path_finder) }
34
+
35
+ let(:path_finder) { build(:path_finder, url: "http://example.com") }
36
+
37
+ context "with child routes" do
38
+ let(:route) do
39
+ described_class.new.tap { |root| root.to(:index) }
40
+ end
41
+
42
+ it { is_expected.to be(true) }
43
+ end
44
+
45
+ context "without child routes" do
46
+ let(:route) { described_class.new }
47
+
48
+ it { is_expected.to be(false) }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ describe Wayfarer::Routing::Route do
6
+ subject(:result) { root.invoke(task) }
7
+
8
+ let(:root) { Wayfarer::Routing::RootRoute.new }
9
+ let(:task) { build(:task, :uri, url: url) }
10
+
11
+ describe "#invoke" do
12
+ context "when matching" do
13
+ let(:url) { "http://example.com/foo/bar?page=42" }
14
+
15
+ before do
16
+ root.host "example.com", to: :overridden do
17
+ path "/:a/:b" do
18
+ to :overrider, query: { page: 42 }
19
+ end
20
+ end
21
+ end
22
+
23
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Match) }
24
+
25
+ describe "action" do
26
+ subject { result.action }
27
+
28
+ it { is_expected.to be(:overrider) }
29
+ end
30
+
31
+ describe "params" do
32
+ subject { result.params }
33
+
34
+ it { is_expected.to eq("a" => "foo", "b" => "bar", "page" => "42") }
35
+ end
36
+ end
37
+
38
+ context "when mismatching" do
39
+ let(:url) { "http://w3c.org/foo/bar?page=42" }
40
+
41
+ before do
42
+ root.host "example.com", to: :overridden do
43
+ path "/:a/:b" do
44
+ query({ page: 42, to: :overrider })
45
+ end
46
+ end
47
+ end
48
+
49
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Mismatch) }
50
+ end
51
+ end
52
+
53
+ describe "custom routing" do
54
+ let(:url) { "http://example.com" }
55
+
56
+ before do
57
+ root.custom do |route, uri|
58
+ if uri.to_s == "http://example.com"
59
+ route.to :foo
60
+ else
61
+ route.to :bar
62
+ end
63
+ end
64
+ end
65
+
66
+ it { is_expected.to be_a(Wayfarer::Routing::Result::Match) }
67
+
68
+ describe "action" do
69
+ subject { result.action }
70
+
71
+ it { is_expected.to be(:foo) }
72
+ end
73
+ end
74
+ end