wayfarer 0.4.7 → 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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/.env +17 -0
  3. data/.github/workflows/lint.yaml +8 -6
  4. data/.github/workflows/release.yaml +4 -3
  5. data/.github/workflows/tests.yaml +5 -14
  6. data/.gitignore +2 -2
  7. data/.rubocop.yml +31 -0
  8. data/.vale.ini +6 -3
  9. data/Dockerfile +3 -2
  10. data/Gemfile +21 -0
  11. data/Gemfile.lock +233 -128
  12. data/Rakefile +7 -0
  13. data/docker-compose.yml +13 -14
  14. data/docs/guides/callbacks.md +3 -1
  15. data/docs/guides/configuration.md +10 -35
  16. data/docs/guides/development.md +67 -0
  17. data/docs/guides/handlers.md +7 -7
  18. data/docs/guides/jobs.md +54 -11
  19. data/docs/guides/networking/custom_adapters.md +31 -10
  20. data/docs/guides/pages.md +24 -22
  21. data/docs/guides/routing.md +116 -34
  22. data/docs/guides/tasks.md +30 -10
  23. data/docs/guides/tutorial.md +23 -17
  24. data/docs/guides/user_agents.md +11 -9
  25. data/lib/wayfarer/base.rb +9 -8
  26. data/lib/wayfarer/batch_completion.rb +18 -14
  27. data/lib/wayfarer/callbacks.rb +14 -14
  28. data/lib/wayfarer/cli/route_printer.rb +78 -96
  29. data/lib/wayfarer/cli.rb +12 -30
  30. data/lib/wayfarer/gc.rb +6 -1
  31. data/lib/wayfarer/kv.rb +28 -0
  32. data/lib/wayfarer/middleware/chain.rb +7 -1
  33. data/lib/wayfarer/middleware/content_type.rb +20 -15
  34. data/lib/wayfarer/middleware/dedup.rb +9 -3
  35. data/lib/wayfarer/middleware/dispatch.rb +7 -2
  36. data/lib/wayfarer/middleware/normalize.rb +4 -12
  37. data/lib/wayfarer/middleware/router.rb +1 -1
  38. data/lib/wayfarer/middleware/uri_parser.rb +4 -3
  39. data/lib/wayfarer/networking/context.rb +12 -1
  40. data/lib/wayfarer/networking/ferrum.rb +1 -4
  41. data/lib/wayfarer/networking/follow.rb +2 -1
  42. data/lib/wayfarer/networking/pool.rb +12 -7
  43. data/lib/wayfarer/networking/selenium.rb +15 -7
  44. data/lib/wayfarer/page.rb +0 -2
  45. data/lib/wayfarer/parsing/xml.rb +1 -1
  46. data/lib/wayfarer/parsing.rb +2 -5
  47. data/lib/wayfarer/redis/barrier.rb +15 -2
  48. data/lib/wayfarer/redis/counter.rb +1 -2
  49. data/lib/wayfarer/routing/dsl.rb +166 -31
  50. data/lib/wayfarer/routing/hash_stack.rb +33 -0
  51. data/lib/wayfarer/routing/matchers/custom.rb +8 -5
  52. data/lib/wayfarer/routing/matchers/{suffix.rb → empty_params.rb} +2 -6
  53. data/lib/wayfarer/routing/matchers/host.rb +15 -9
  54. data/lib/wayfarer/routing/matchers/path.rb +11 -33
  55. data/lib/wayfarer/routing/matchers/query.rb +41 -17
  56. data/lib/wayfarer/routing/matchers/result.rb +12 -0
  57. data/lib/wayfarer/routing/matchers/scheme.rb +13 -5
  58. data/lib/wayfarer/routing/matchers/url.rb +13 -5
  59. data/lib/wayfarer/routing/path_consumer.rb +130 -0
  60. data/lib/wayfarer/routing/path_finder.rb +151 -23
  61. data/lib/wayfarer/routing/result.rb +1 -1
  62. data/lib/wayfarer/routing/root_route.rb +14 -2
  63. data/lib/wayfarer/routing/route.rb +71 -14
  64. data/lib/wayfarer/routing/serializable.rb +28 -0
  65. data/lib/wayfarer/routing/sub_route.rb +53 -0
  66. data/lib/wayfarer/routing/target_route.rb +17 -1
  67. data/lib/wayfarer/stringify.rb +1 -2
  68. data/lib/wayfarer/task.rb +3 -5
  69. data/lib/wayfarer/uri/normalization.rb +120 -0
  70. data/lib/wayfarer.rb +50 -10
  71. data/mise.toml +2 -0
  72. data/mkdocs.yml +8 -17
  73. data/rake/lint.rake +0 -96
  74. data/rake/release.rake +5 -11
  75. data/rake/tests.rake +8 -4
  76. data/requirements.txt +1 -1
  77. data/spec/factories/job.rb +8 -0
  78. data/spec/factories/middleware.rb +2 -2
  79. data/spec/factories/path_finder.rb +11 -0
  80. data/spec/factories/redis.rb +19 -0
  81. data/spec/factories/task.rb +39 -1
  82. data/spec/spec_helpers.rb +50 -57
  83. data/spec/support/active_job_helpers.rb +8 -0
  84. data/spec/support/integration_helpers.rb +21 -0
  85. data/spec/support/redis_helpers.rb +9 -0
  86. data/spec/support/test_app.rb +64 -43
  87. data/spec/{base_spec.rb → wayfarer/base_spec.rb} +32 -36
  88. data/spec/wayfarer/batch_completion_spec.rb +142 -0
  89. data/spec/wayfarer/cli/job_spec.rb +88 -0
  90. data/spec/wayfarer/cli/routing_spec.rb +322 -0
  91. data/spec/{cli → wayfarer/cli}/version_spec.rb +1 -1
  92. data/spec/wayfarer/gc_spec.rb +29 -0
  93. data/spec/{handler_spec.rb → wayfarer/handler_spec.rb} +1 -3
  94. data/spec/{integration → wayfarer/integration}/callbacks_spec.rb +9 -6
  95. data/spec/wayfarer/integration/content_type_spec.rb +37 -0
  96. data/spec/wayfarer/integration/custom_routing_spec.rb +51 -0
  97. data/spec/{integration → wayfarer/integration}/gc_spec.rb +9 -13
  98. data/spec/{integration → wayfarer/integration}/handler_spec.rb +9 -10
  99. data/spec/{integration → wayfarer/integration}/page_spec.rb +8 -6
  100. data/spec/{integration → wayfarer/integration}/params_spec.rb +4 -4
  101. data/spec/{integration → wayfarer/integration}/parsing_spec.rb +7 -33
  102. data/spec/wayfarer/integration/retry_spec.rb +112 -0
  103. data/spec/{integration → wayfarer/integration}/stage_spec.rb +5 -5
  104. data/spec/{middleware → wayfarer/middleware}/batch_completion_spec.rb +4 -5
  105. data/spec/{middleware → wayfarer/middleware}/chain_spec.rb +20 -15
  106. data/spec/{middleware → wayfarer/middleware}/content_type_spec.rb +18 -21
  107. data/spec/{middleware → wayfarer/middleware}/controller_spec.rb +22 -20
  108. data/spec/wayfarer/middleware/dedup_spec.rb +66 -0
  109. data/spec/wayfarer/middleware/normalize_spec.rb +32 -0
  110. data/spec/{middleware → wayfarer/middleware}/router_spec.rb +18 -20
  111. data/spec/{middleware → wayfarer/middleware}/stage_spec.rb +11 -10
  112. data/spec/wayfarer/middleware/uri_parser_spec.rb +63 -0
  113. data/spec/{middleware → wayfarer/middleware}/user_agent_spec.rb +34 -32
  114. data/spec/wayfarer/networking/capybara_spec.rb +13 -0
  115. data/spec/{networking → wayfarer/networking}/context_spec.rb +46 -38
  116. data/spec/wayfarer/networking/ferrum_spec.rb +13 -0
  117. data/spec/{networking → wayfarer/networking}/follow_spec.rb +9 -4
  118. data/spec/wayfarer/networking/http_spec.rb +12 -0
  119. data/spec/{networking → wayfarer/networking}/pool_spec.rb +11 -9
  120. data/spec/wayfarer/networking/selenium_spec.rb +12 -0
  121. data/spec/{networking → wayfarer/networking}/strategy.rb +33 -54
  122. data/spec/{page_spec.rb → wayfarer/page_spec.rb} +3 -3
  123. data/spec/{parsing → wayfarer/parsing}/json_spec.rb +1 -1
  124. data/spec/{parsing/xml_spec.rb → wayfarer/parsing/xml_parse_spec.rb} +4 -3
  125. data/spec/{redis → wayfarer/redis}/barrier_spec.rb +5 -4
  126. data/spec/wayfarer/redis/counter_spec.rb +34 -0
  127. data/spec/{redis → wayfarer/redis}/pool_spec.rb +3 -2
  128. data/spec/{routing → wayfarer/routing}/dsl_spec.rb +12 -22
  129. data/spec/wayfarer/routing/hash_stack_spec.rb +63 -0
  130. data/spec/wayfarer/routing/integration_spec.rb +101 -0
  131. data/spec/wayfarer/routing/matchers/custom_spec.rb +39 -0
  132. data/spec/wayfarer/routing/matchers/host_spec.rb +56 -0
  133. data/spec/wayfarer/routing/matchers/matcher.rb +17 -0
  134. data/spec/wayfarer/routing/matchers/path_spec.rb +43 -0
  135. data/spec/wayfarer/routing/matchers/query_spec.rb +123 -0
  136. data/spec/wayfarer/routing/matchers/scheme_spec.rb +45 -0
  137. data/spec/wayfarer/routing/matchers/url_spec.rb +33 -0
  138. data/spec/wayfarer/routing/path_consumer_spec.rb +123 -0
  139. data/spec/wayfarer/routing/path_finder_spec.rb +409 -0
  140. data/spec/wayfarer/routing/root_route_spec.rb +51 -0
  141. data/spec/wayfarer/routing/route_spec.rb +74 -0
  142. data/spec/wayfarer/routing/sub_route_spec.rb +103 -0
  143. data/spec/wayfarer/uri/normalization_spec.rb +98 -0
  144. data/spec/wayfarer_spec.rb +2 -2
  145. data/wayfarer.gemspec +17 -28
  146. metadata +768 -246
  147. data/.rbenv-gemsets +0 -1
  148. data/.ruby-version +0 -1
  149. data/RELEASING.md +0 -17
  150. data/docs/cookbook/user_agent.md +0 -7
  151. data/docs/design.md +0 -36
  152. data/docs/guides/jobs/error_handling.md +0 -40
  153. data/docs/reference/configuration.md +0 -36
  154. data/spec/batch_completion_spec.rb +0 -104
  155. data/spec/cli/job_spec.rb +0 -74
  156. data/spec/cli/routing_spec.rb +0 -101
  157. data/spec/fixtures/dummy_job.rb +0 -9
  158. data/spec/gc_spec.rb +0 -17
  159. data/spec/integration/content_type_spec.rb +0 -145
  160. data/spec/integration/routing_spec.rb +0 -18
  161. data/spec/middleware/dedup_spec.rb +0 -71
  162. data/spec/middleware/dispatch_spec.rb +0 -59
  163. data/spec/middleware/normalize_spec.rb +0 -60
  164. data/spec/middleware/uri_parser_spec.rb +0 -53
  165. data/spec/networking/capybara_spec.rb +0 -12
  166. data/spec/networking/ferrum_spec.rb +0 -12
  167. data/spec/networking/http_spec.rb +0 -12
  168. data/spec/networking/selenium_spec.rb +0 -12
  169. data/spec/redis/counter_spec.rb +0 -44
  170. data/spec/routing/integration_spec.rb +0 -110
  171. data/spec/routing/matchers/custom_spec.rb +0 -31
  172. data/spec/routing/matchers/host_spec.rb +0 -49
  173. data/spec/routing/matchers/path_spec.rb +0 -43
  174. data/spec/routing/matchers/query_spec.rb +0 -137
  175. data/spec/routing/matchers/scheme_spec.rb +0 -25
  176. data/spec/routing/matchers/suffix_spec.rb +0 -41
  177. data/spec/routing/matchers/uri_spec.rb +0 -27
  178. data/spec/routing/path_finder_spec.rb +0 -33
  179. data/spec/routing/root_route_spec.rb +0 -29
  180. data/spec/routing/route_spec.rb +0 -43
  181. data/docs/{reference → guides}/cli.md +0 -0
  182. data/spec/{stringify_spec.rb → wayfarer/stringify_spec.rb} +2 -2
  183. /data/spec/{task_spec.rb → wayfarer/task_spec.rb} +0 -0
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ describe "Custom routing", :redis do
6
+ let(:task) { build(:task) }
7
+
8
+ before do
9
+ stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
10
+ DummyJob.class_eval do
11
+ class_attribute :action,
12
+ :params
13
+
14
+ route.custom do |root, uri, _task|
15
+ if uri.host == "example.com"
16
+ root.path ":segment" do
17
+ path "users", path: "/:id", to: :foo
18
+ end
19
+ else
20
+ root.to :bar
21
+ end
22
+ end
23
+
24
+ after_action do
25
+ self.class.params = params
26
+ end
27
+
28
+ def foo
29
+ self.class.action = :foo
30
+ end
31
+
32
+ def bar
33
+ self.class.action = :bar
34
+ end
35
+ end
36
+ end
37
+
38
+ specify do
39
+ DummyJob.crawl("https://example.com/foobar/users/42")
40
+ perform_enqueued_jobs
41
+ expect(DummyJob.action).to be(:foo)
42
+ assert_performed_jobs 1
43
+ expect(enqueued_jobs).to be_empty
44
+
45
+ DummyJob.crawl(test_app_path("/"))
46
+ perform_enqueued_jobs
47
+ expect(DummyJob.action).to be(:bar)
48
+ assert_performed_jobs 2
49
+ expect(enqueued_jobs).to be_empty
50
+ end
51
+ end
@@ -2,32 +2,28 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "Garbage collection", redis: true do
5
+ describe "Garbage collection", :redis do
6
+ subject(:perform) do
7
+ DummyJob.perform_later(task)
8
+ perform_enqueued_jobs
9
+ end
10
+
6
11
  let(:task) { build(:task, :redis_pool) }
7
- let(:counter) { Wayfarer::Redis::Counter.new(task) }
8
- let(:barrier) { Wayfarer::Redis::Barrier.new(task) }
9
- let(:redis) { ::Redis.new(url: redis_host) }
12
+ let(:counter) { build(:counter, task: task) }
13
+ let(:barrier) { build(:barrier, task: task) }
10
14
 
11
15
  before do
12
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
16
+ mock_job! :dummy_job
13
17
 
14
18
  DummyJob.class_eval do
15
19
  route.to :index
16
20
 
17
21
  def index; end
18
22
  end
19
- end
20
-
21
- before do
22
23
  redis.set(counter.redis_key, 0)
23
24
  redis.hset(barrier.redis_key, "", "")
24
25
  end
25
26
 
26
- subject(:perform) do
27
- DummyJob.perform_later(task)
28
- perform_enqueued_jobs
29
- end
30
-
31
27
  it "resets counter" do
32
28
  expect { perform }.to change { redis.exists?(counter.redis_key) }.from(true).to(false)
33
29
 
@@ -2,11 +2,13 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "Handlers", redis: true do
5
+ describe "Handlers", :redis do
6
6
  before do
7
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
8
- stub_const("DummyHandler", Class.new.include(Wayfarer::Handler))
9
- stub_const("OtherHandler", Class.new.include(Wayfarer::Handler))
7
+ mock_job! :dummy_job
8
+ mock_job! :other_job
9
+
10
+ mock_handler! :dummy_handler
11
+ mock_handler! :other_handler
10
12
  end
11
13
 
12
14
  describe "bypassing the router" do
@@ -45,11 +47,6 @@ describe "Handlers", redis: true do
45
47
  end
46
48
 
47
49
  describe "dispatching to another Wayfarer::Base" do
48
- before do
49
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
50
- stub_const("OtherJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
51
- end
52
-
53
50
  before do
54
51
  DummyJob.class_eval do
55
52
  extend SpecHelpers
@@ -58,9 +55,11 @@ describe "Handlers", redis: true do
58
55
  end
59
56
  end
60
57
 
58
+ let(:error) { Wayfarer::Middleware::Dispatch::InvalidTargetError }
59
+
61
60
  specify do
62
61
  DummyJob.crawl(test_app_path("/"))
63
- expect { perform_enqueued_jobs }.to raise_error(ArgumentError, "invalid action: OtherJob")
62
+ expect { perform_enqueued_jobs }.to raise_error(error, "routed to invalid action: #{OtherJob}")
64
63
  end
65
64
  end
66
65
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "Pages" do
6
- let(:url) { test_app_path("git-scm.com/book/en/v2.html") }
7
-
5
+ describe "Pages", :redis do
8
6
  before do
9
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
10
- stub_const("DummyHandler", Class.new.include(Wayfarer::Handler))
7
+ mock_job! :dummy_job
8
+ mock_handler! :dummy_handler
11
9
  end
12
10
 
11
+ let(:url) { test_app_path("git-scm.com/book/en/v2.html") }
12
+
13
13
  shared_examples "executes" do
14
14
  specify do
15
15
  DummyJob.crawl(url)
@@ -46,6 +46,8 @@ describe "Pages" do
46
46
 
47
47
  describe "page content with handler" do
48
48
  before do
49
+ handler = DummyHandler
50
+
49
51
  DummyJob.class_eval do
50
52
  include RSpec::Matchers
51
53
  include SpecHelpers
@@ -68,7 +70,7 @@ describe "Pages" do
68
70
  extend SpecHelpers
69
71
  include SpecHelpers
70
72
 
71
- route.host test_app_host, to: DummyHandler
73
+ route.host test_app_host, to: handler
72
74
  end
73
75
  end
74
76
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "URL parameters" do
5
+ describe "Path parameters", :redis do
6
6
  let(:url) { test_app_path("git-scm.com/book/en/v2.html") }
7
7
 
8
8
  before do
@@ -25,7 +25,7 @@ describe "URL parameters" do
25
25
  extend SpecHelpers
26
26
  include RSpec::Matchers
27
27
 
28
- route.to :index, host: test_app_host do
28
+ route.to :index, host: test_app_host, scheme: :https do
29
29
  path "git-scm.com/book/:lang/:file"
30
30
  end
31
31
 
@@ -58,7 +58,7 @@ describe "URL parameters" do
58
58
  end
59
59
  end
60
60
  end
61
- end
62
61
 
63
- it_behaves_like "executes"
62
+ it_behaves_like "executes"
63
+ end
64
64
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "Response body parsing", redis: true do
5
+ describe "Response body parsing", :redis do
6
6
  before do
7
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
8
- stub_const("DummyHandler", Class.new.include(Wayfarer::Handler))
7
+ mock_job! :dummy_job
8
+ mock_handler! :dummy_handler
9
9
  end
10
10
 
11
11
  shared_examples "executes" do
@@ -17,24 +17,6 @@ describe "Response body parsing", redis: true do
17
17
  end
18
18
  end
19
19
 
20
- context "with XML" do
21
- let(:url) { test_app_path("xml/dummy.xml") }
22
-
23
- before do
24
- DummyJob.class_eval do
25
- include RSpec::Matchers
26
-
27
- route.to :index
28
-
29
- def index
30
- expect(page.doc).to be_a(Nokogiri::XML::Document)
31
- end
32
- end
33
-
34
- it_behaves_like "executes"
35
- end
36
- end
37
-
38
20
  context "with HTML" do
39
21
  let(:url) { test_app_path("finders.html") }
40
22
 
@@ -90,16 +72,8 @@ describe "Response body parsing", redis: true do
90
72
  end
91
73
 
92
74
  before do
93
- Wayfarer::Parsing.registry["foo/bar"] = parser
94
- Wayfarer::Parsing.registry["bar/qux"] = [parser_with_options, :ok]
95
- end
96
-
97
- after do
98
- Wayfarer::Parsing.registry.delete("foo/bar")
99
- Wayfarer::Parsing.registry.delete("bar/qux")
100
- end
101
-
102
- before do
75
+ Wayfarer.config[:parsing][:registry]["foo/bar"] = parser
76
+ Wayfarer.config[:parsing][:registry]["bar/qux"] = [parser_with_options, :ok]
103
77
  DummyJob.class_eval do
104
78
  route.to :index
105
79
 
@@ -115,10 +89,10 @@ describe "Response body parsing", redis: true do
115
89
  )
116
90
  end
117
91
 
118
- specify do
92
+ specify :aggregate_failures do
119
93
  expect(perform("foo/bar")).to be(:ok)
120
94
  expect(perform("bar/qux")).to be(:ok)
121
- expect(perform("image/jpeg")).to be(nil)
95
+ expect(perform("image/jpeg")).to be_nil
122
96
  end
123
97
  end
124
98
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helpers"
4
+
5
+ describe "Retries", :redis do
6
+ # rubocop:disable RSpec/MultipleExpectations
7
+ let(:task) { build(:task, :normalized, :redis_pool) }
8
+ let(:counter) { build(:counter, task: task) }
9
+ let(:barrier) { build(:barrier, task: task) }
10
+
11
+ before do
12
+ stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
13
+
14
+ Wayfarer::Redis::Counter.new(task).increment # avoid garbage collection
15
+ end
16
+
17
+ context "when job fails" do
18
+ before do
19
+ DummyJob.class_eval do
20
+ class_attribute :attempts, default: 0
21
+
22
+ retry_on RuntimeError, wait: 0, attempts: 3
23
+
24
+ route.to :index
25
+
26
+ def index
27
+ self.class.attempts += 1
28
+
29
+ raise
30
+ end
31
+ end
32
+ end
33
+
34
+ specify do
35
+ DummyJob.perform_later(task)
36
+
37
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(0).to(1)
38
+
39
+ assert_performed_jobs 1
40
+ expect(enqueued_jobs.pluck("executions")).to contain_exactly(1)
41
+
42
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(1).to(2)
43
+ assert_performed_jobs 2
44
+ expect(enqueued_jobs.pluck("executions")).to contain_exactly(2)
45
+
46
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(2).to(3).and raise_error(RuntimeError)
47
+ assert_performed_jobs 3
48
+ expect(enqueued_jobs).to be_empty
49
+
50
+ expect(redis.hget(barrier.redis_key, task[:uri].to_s)).to eq(Wayfarer::Redis::Barrier::VALUE)
51
+ end
52
+ end
53
+
54
+ context "when job fails and eventually succeeds" do
55
+ before do
56
+ DummyJob.class_eval do
57
+ class_attribute :attempts, default: 0
58
+
59
+ retry_on RuntimeError, wait: 0, attempts: 3
60
+
61
+ route.to :index
62
+
63
+ def index
64
+ raise if [1, 2].include?(self.class.attempts += 1)
65
+ end
66
+ end
67
+ end
68
+
69
+ specify do
70
+ DummyJob.perform_later(task)
71
+
72
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(0).to(1)
73
+ assert_performed_jobs 1
74
+ expect(enqueued_jobs.pluck("executions")).to contain_exactly(1)
75
+
76
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(1).to(2)
77
+ assert_performed_jobs 2
78
+ expect(enqueued_jobs.pluck("executions")).to contain_exactly(2)
79
+
80
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(2).to(3)
81
+ assert_performed_jobs 3
82
+ expect(enqueued_jobs).to be_empty
83
+
84
+ expect(redis.hget(barrier.redis_key, task[:uri].to_s)).to eq(Wayfarer::Redis::Barrier::VALUE)
85
+ end
86
+ end
87
+
88
+ context "when job succeeds" do
89
+ before do
90
+ DummyJob.class_eval do
91
+ class_attribute :attempts, default: 0
92
+
93
+ route.to :index
94
+
95
+ def index
96
+ self.class.attempts += 1
97
+ end
98
+ end
99
+ end
100
+
101
+ specify do
102
+ DummyJob.perform_later(task)
103
+
104
+ expect { perform_enqueued_jobs }.to change(DummyJob, :attempts).from(0).to(1)
105
+ assert_performed_jobs 1
106
+ expect(enqueued_jobs).to be_empty
107
+
108
+ expect(redis.hget(barrier.redis_key, task[:uri].to_s)).to eq(Wayfarer::Redis::Barrier::VALUE)
109
+ end
110
+ end
111
+ # rubocop:enable RSpec/MultipleExpectations
112
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe "Staging", redis: true do
5
+ describe "Staging", :redis do
6
6
  let(:url) { test_app_path("git-scm.com/book/en/v2.html") }
7
7
 
8
8
  before do
@@ -15,7 +15,7 @@ describe "Staging", redis: true do
15
15
  DummyJob.class_eval do
16
16
  extend SpecHelpers
17
17
 
18
- route.host test_app_host, to: :index
18
+ route.host test_app_hostname, to: :index
19
19
 
20
20
  def index
21
21
  stage page.meta.links.all
@@ -27,7 +27,7 @@ describe "Staging", redis: true do
27
27
  expect {
28
28
  DummyJob.crawl(url)
29
29
  perform_enqueued_jobs
30
- }.to change { enqueued_jobs.size }.by(157)
30
+ }.to change(enqueued_jobs, :size).by(157)
31
31
  end
32
32
  end
33
33
 
@@ -36,7 +36,7 @@ describe "Staging", redis: true do
36
36
  DummyJob.class_eval do
37
37
  extend SpecHelpers
38
38
 
39
- route.host test_app_host, to: DummyHandler
39
+ route.host test_app_hostname, to: DummyHandler
40
40
  end
41
41
 
42
42
  DummyHandler.class_eval do
@@ -52,7 +52,7 @@ describe "Staging", redis: true do
52
52
  expect {
53
53
  DummyJob.crawl(url)
54
54
  perform_enqueued_jobs
55
- }.to change { enqueued_jobs.size }.by(157)
55
+ }.to change(enqueued_jobs, :size).by(157)
56
56
  end
57
57
  end
58
58
  end
@@ -2,13 +2,12 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe Wayfarer::Middleware::BatchCompletion, "#call" do
6
- let(:task) { build(:task) }
7
- let(:job) { double(exception_executions: exception_executions) }
8
- let(:exception_executions) { {} }
5
+ describe Wayfarer::Middleware::BatchCompletion do
9
6
  subject(:batch_completion) { described_class.new }
10
7
 
11
- before { task[:job] = job }
8
+ let(:task) { build(:task, job: job) }
9
+ let(:job) { build(:job, exception_executions: exception_executions) }
10
+ let(:exception_executions) { {} }
12
11
 
13
12
  it "assigns cloned exception_executions" do
14
13
  expect {
@@ -3,9 +3,10 @@
3
3
  require "spec_helpers"
4
4
 
5
5
  describe Wayfarer::Middleware::Chain do
6
+ subject(:chain) { described_class.new(middlewares) }
7
+
6
8
  let(:task) { build(:task) }
7
9
  let(:middlewares) { [] }
8
- subject(:chain) { described_class.new(middlewares) }
9
10
 
10
11
  describe "::empty" do
11
12
  it "returns an empty chain" do
@@ -13,7 +14,7 @@ describe Wayfarer::Middleware::Chain do
13
14
  end
14
15
  end
15
16
 
16
- describe "#call" do
17
+ describe "#push" do
17
18
  it "adds middleware" do
18
19
  expect {
19
20
  chain.push(Class.new)
@@ -35,20 +36,23 @@ describe Wayfarer::Middleware::Chain do
35
36
  context "when middleware yields" do
36
37
  let(:middlewares) { [spy, spy] }
37
38
 
38
- it "yields" do
39
+ it "yields", :aggregate_failures do
39
40
  allow(middlewares.first).to receive(:call).and_yield
40
- expect(middlewares.last).to receive(:call).with(task).and_yield
41
+ allow(middlewares.last).to receive(:call).with(task).and_yield
42
+
41
43
  expect { |spy| chain.call(task, &spy) }.to yield_control
44
+ expect(middlewares.last).to have_received(:call).with(task)
42
45
  end
43
46
  end
44
47
 
45
48
  context "when middleware does not yield" do
46
49
  let(:middlewares) { [spy, spy] }
47
50
 
48
- it "does not yield" do
51
+ it "does not yield", :aggregate_failures do
49
52
  allow(middlewares.first).to receive(:call)
50
- expect(middlewares.last).not_to receive(:call)
53
+
51
54
  expect { |spy| chain.call(task, &spy) }.not_to yield_control
55
+ expect(middlewares.last).not_to have_received(:call)
52
56
  end
53
57
  end
54
58
 
@@ -58,7 +62,8 @@ describe Wayfarer::Middleware::Chain do
58
62
  it "does not alter its middleware" do
59
63
  allow(middlewares.first).to receive(:call).and_yield
60
64
  allow(middlewares.second).to receive(:call).and_yield
61
- expect { chain.call(task) }.not_to(change { chain.middlewares })
65
+
66
+ expect { chain.call(task) }.not_to(change(chain, :middlewares))
62
67
  end
63
68
  end
64
69
 
@@ -81,22 +86,22 @@ describe Wayfarer::Middleware::Chain do
81
86
  describe "Metadata" do
82
87
  let(:first) do
83
88
  build(:middleware, receiver: lambda do |task, &block|
84
- task[:foobar] = 42
85
- block.call
86
- task[:barqux] = 1337
87
- end)
89
+ task[:foobar] = 42
90
+ block.call
91
+ task[:barqux] = 1337
92
+ end)
88
93
  end
89
94
 
90
95
  let(:last) do
91
96
  build(:middleware, receiver: lambda do |task|
92
- raise unless task[:foobar] == 42
93
- raise if task[:barqux]
94
- end)
97
+ raise unless task[:foobar] == 42
98
+ raise if task[:barqux]
99
+ end)
95
100
  end
96
101
 
97
102
  let(:middlewares) { [first, last] }
98
103
 
99
- it "is accessible across middleware" do
104
+ it "is accessible across middleware", :aggregate_failures do
100
105
  expect {
101
106
  chain.call(task)
102
107
  }.not_to raise_error
@@ -4,66 +4,63 @@ require "spec_helpers"
4
4
 
5
5
  describe Wayfarer::Middleware::ContentType do
6
6
  let(:content_type) { "text/html" }
7
- let(:task) { build(:task) }
7
+ let(:task) { build(:task, :normalized) }
8
8
  let(:page) { build(:page, headers: { "Content-Type" => content_type }) }
9
9
 
10
10
  describe "#call" do
11
- subject { Class.new(described_class).include(described_class::API).new }
11
+ subject(:middleware) { Class.new(described_class).include(described_class::API).new }
12
12
 
13
13
  before do
14
14
  task[:page] = page
15
- task[:controller] = subject
15
+ task[:controller] = middleware
16
+ middleware.class.content_type "text/html"
16
17
  end
17
18
 
18
- before { subject.class.content_type "text/html" }
19
-
20
19
  context "with permitted Content-Type" do
21
- it "yields" do
22
- expect { |spy| subject.call(task, &spy) }.to yield_control
20
+ specify do
21
+ expect { |spy| middleware.call(task, &spy) }.to yield_control
23
22
  end
24
23
  end
25
24
 
26
25
  context "when permitted Content-Type has parameters" do
27
- let(:page) { build(:page, headers: { "Content-Type" => "#{content_type}; charset=UTF-" }) }
26
+ let(:page) { build(:page, headers: { "Content-Type" => "#{content_type}; charset=UTF-8" }) }
28
27
 
29
- it "yields" do
30
- expect { |spy| subject.call(task, &spy) }.to yield_control
28
+ specify do
29
+ expect { |spy| middleware.call(task, &spy) }.to yield_control
31
30
  end
32
31
  end
33
32
 
34
33
  context "with forbidden Content-Type" do
35
34
  let(:content_type) { "application/json" }
36
35
 
37
- it "does not yield" do
38
- expect { |spy| subject.call(task, &spy) }.not_to yield_control
36
+ specify do
37
+ expect { |spy| middleware.call(task, &spy) }.not_to yield_control
39
38
  end
40
39
  end
41
40
 
42
41
  context "with permitted Regexp Content-Type" do
43
42
  before do
44
- subject.class.content_type(/text/)
43
+ middleware.class.content_type(/text/)
45
44
  end
46
45
 
47
- it "yields" do
48
- expect { |spy| subject.call(task, &spy) }.to yield_control
46
+ specify do
47
+ expect { |spy| middleware.call(task, &spy) }.to yield_control
49
48
  end
50
49
  end
51
50
  end
52
51
 
53
52
  describe described_class::API do
54
- subject(:controller) do
55
- Struct.new(:task).include(described_class).new(task)
56
- end
53
+ subject(:controller) { Class.new.include(described_class).new }
57
54
 
58
55
  describe "::allowed_content_types" do
59
56
  describe "index" do
60
- subject { controller.class.allowed_content_types[:index] }
57
+ subject(:index) { controller.class.allowed_content_types[:index] }
61
58
 
62
59
  it { is_expected.to be_empty }
63
60
  end
64
61
 
65
62
  describe "patterns" do
66
- subject { controller.class.allowed_content_types[:patterns] }
63
+ subject(:patterns) { controller.class.allowed_content_types[:patterns] }
67
64
 
68
65
  it { is_expected.to be_empty }
69
66
  end
@@ -73,7 +70,7 @@ describe Wayfarer::Middleware::ContentType do
73
70
  it "allows Content-Types" do
74
71
  controller.class.content_type(content_type)
75
72
 
76
- expect(controller.class.allowed_content_types[:index][content_type]).to be(true)
73
+ expect(controller.class.allowed_content_types.dig(:index, content_type)).to be(true)
77
74
  end
78
75
 
79
76
  it "allows Content-Type patterns" do