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
data/rake/tests.rake CHANGED
@@ -8,21 +8,25 @@ RSpec::Core::RakeTask.new(:test)
8
8
  namespace :test do
9
9
  desc "Run only isolated tests"
10
10
  RSpec::Core::RakeTask.new :isolated do |task|
11
- task.rspec_opts = ["--tag ~selenium --tag ~ferrum --tag ~cli"]
11
+ task.rspec_opts = %w[--tag ~selenium --tag ~ferrum --tag ~cli --tag ~redis]
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new :integration do |task|
15
+ task.rspec_opts = %w[--tag redis --tag cli]
12
16
  end
13
17
 
14
18
  desc "Run only Selenium tests"
15
19
  RSpec::Core::RakeTask.new :selenium do |task|
16
- task.rspec_opts = ["--tag selenium"]
20
+ task.rspec_opts = %w[--tag selenium]
17
21
  end
18
22
 
19
23
  desc "Run only Ferrum tests"
20
24
  RSpec::Core::RakeTask.new :ferrum do |task|
21
- task.rspec_opts = ["--tag ferrum"]
25
+ task.rspec_opts = %w[--tag ferrum]
22
26
  end
23
27
 
24
28
  desc "Run only CLI tests"
25
29
  RSpec::Core::RakeTask.new :cli do |task|
26
- task.rspec_opts = ["--tag cli"]
30
+ task.rspec_opts = %w[--tag cli]
27
31
  end
28
32
  end
data/requirements.txt CHANGED
@@ -1 +1 @@
1
- mkdocs-material == 9.5.9
1
+ mkdocs-material == 9.6.12
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :job, class: ActiveJob::Base do
5
+ executions { 1 }
6
+ exception_executions { Object.new }
7
+ end
8
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  FactoryBot.define do
4
- factory :middleware, class: OpenStruct do
4
+ factory :middleware, class: Struct.new(:receiver) do
5
5
  receiver { -> {} }
6
6
 
7
7
  initialize_with do
8
- new(receiver: receiver).tap do |middleware|
8
+ new(receiver).tap do |middleware|
9
9
  middleware.define_singleton_method(:call) do |task, &block|
10
10
  receiver.call(task, &block)
11
11
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :path_finder, class: Wayfarer::Routing::PathFinder do
5
+ url { "https://example.com" }
6
+
7
+ initialize_with do
8
+ new(build(:task, :uri, url: url))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :counter, class: Wayfarer::Redis::Counter do
5
+ task { build(:task, :redis_pool) }
6
+
7
+ initialize_with do
8
+ new(task)
9
+ end
10
+ end
11
+
12
+ factory :barrier, class: Wayfarer::Redis::Barrier do
13
+ task { build(:task, :redis_pool) }
14
+
15
+ initialize_with do
16
+ new(task)
17
+ end
18
+ end
19
+ end
@@ -5,8 +5,24 @@ FactoryBot.define do
5
5
  url { test_app_path("/") }
6
6
  batch { "batch" }
7
7
 
8
+ transient do
9
+ job { nil }
10
+ controller { nil }
11
+ action { nil }
12
+ end
13
+
8
14
  initialize_with do
9
- new(url, batch)
15
+ new(url, batch).tap do |task|
16
+ task[:job] = job
17
+ task[:controller] = controller
18
+ task[:action] = action
19
+ end
20
+ end
21
+
22
+ trait :staged_urls do
23
+ after(:build) do |task|
24
+ task[:staged_urls] = SortedSet.new
25
+ end
10
26
  end
11
27
 
12
28
  trait :redis_pool do
@@ -14,5 +30,27 @@ FactoryBot.define do
14
30
  task[:redis_pool] = Wayfarer::Redis::Pool.instance
15
31
  end
16
32
  end
33
+
34
+ trait :barrier do
35
+ redis_pool
36
+
37
+ after(:build) do |task|
38
+ task[:barrier] = Wayfarer::Redis::Barrier.new(task)
39
+ end
40
+ end
41
+
42
+ trait :uri do
43
+ after(:build) do |task|
44
+ task[:uri] = Addressable::URI.parse(task.url)
45
+ end
46
+ end
47
+
48
+ trait :normalized do
49
+ uri
50
+
51
+ after(:build) do |task|
52
+ task[:uri] = Wayfarer::URI::Normalization.canonical!(task[:uri])
53
+ end
54
+ end
17
55
  end
18
56
  end
data/spec/spec_helpers.rb CHANGED
@@ -1,44 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/deep_dup"
4
+ require "capybara/cuprite"
3
5
  require "factory_bot"
6
+ require "puma"
4
7
  require "pry"
8
+ require "rake"
9
+ require "rspec-parameterized"
5
10
  require "sinatra"
6
- require "capybara/cuprite"
11
+ require "webrick"
7
12
 
8
13
  require_relative "../lib/wayfarer"
14
+
9
15
  require_relative "support/test_app"
16
+ require_relative "support/redis_helpers"
17
+ require_relative "support/active_job_helpers"
18
+ require_relative "support/integration_helpers"
10
19
 
11
20
  module SpecHelpers
12
- def test_app_path(path)
13
- File.join("http://", test_app_host, path)
21
+ WAYFARER_TEST_APP_PORT = ENV.fetch("WAYFARER_PORT", "9876").to_s
22
+
23
+ def ci?
24
+ ENV.key?("CI")
25
+ end
26
+
27
+ def test_redis_host
28
+ ci? ? "redis" : "localhost"
14
29
  end
15
30
 
16
31
  def test_app_host
17
- if ENV["CI"]
18
- "test:9876"
19
- else
20
- "localhost:9876"
21
- end
32
+ ci? ? "test" : "localhost"
22
33
  end
23
34
 
24
- def redis_host
25
- if ENV["CI"]
26
- "redis://redis:6379"
27
- else
28
- "redis://localhost:6379"
29
- end
35
+ def redis_url
36
+ "redis://#{test_redis_host}:6379/0"
30
37
  end
31
38
 
32
- def write_file(path, contents)
33
- FileUtils.mkdir_p(Pathname.new(path).dirname)
34
- IO.write(path, contents)
39
+ def test_app_path(path = "")
40
+ File.join("http://", "#{test_app_host}:#{WAYFARER_TEST_APP_PORT}", path)
35
41
  end
36
- end
37
42
 
38
- module ActiveJobTestHelper
39
- # perform_enqueued_jobs calls assert_equal
40
- def assert_equal(expected, actual, _)
41
- expect(actual).to eq(expected)
43
+ def test_app_hostname
44
+ test_app_host.split(":").first
45
+ end
46
+
47
+ def write_file(path, contents)
48
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
49
+ File.write(path, contents)
42
50
  end
43
51
  end
44
52
 
@@ -47,57 +55,46 @@ RSpec.configure do |config|
47
55
  config.include(ActiveJob::TestHelper)
48
56
  config.include(SpecHelpers)
49
57
  config.include(ActiveJobTestHelper)
58
+ config.include(IntegrationHelpers)
50
59
 
51
- config.before(:suite) do
60
+ # The shared context enables a global `let(:redis)` declaration
61
+ config.include_context "with Redis", :redis
62
+
63
+ config.before :suite do
52
64
  Wayfarer::Logging.logger = Logger.new($stdout)
53
65
 
54
66
  FactoryBot.find_definitions
55
- FactoryBot::SyntaxRunner.include SpecHelpers
56
-
57
- mutex = Mutex.new
58
- cvar = ConditionVariable.new
59
-
60
- Thread.new do
61
- Rack::Handler::WEBrick.run(TestApp,
62
- Host: "0.0.0.0",
63
- Port: 9876,
64
- Logger: WEBrick::Log.new("/dev/null"),
65
- AccessLog: [],
66
- StartCallback: proc { cvar.signal })
67
- end
67
+ FactoryBot::SyntaxRunner.include(SpecHelpers)
68
68
 
69
- mutex.lock
70
- cvar.wait(mutex)
69
+ Support::TestApp.start!(SpecHelpers::WAYFARER_TEST_APP_PORT)
71
70
  end
72
71
 
73
72
  config.before do
74
- Wayfarer.config = Wayfarer::DEFAULT_CONFIG.clone
75
- Wayfarer.config[:redis][:url] = redis_host
73
+ Wayfarer.config = Wayfarer::DEFAULT_CONFIG.deep_dup
74
+ Wayfarer.config[:redis][:url] = redis_url
76
75
 
77
- ActiveJob::Base.queue_adapter = :test
78
76
  ActiveJob::Base.logger = Logger.new(nil)
77
+ ActiveJob::Base.queue_adapter = :test
79
78
  ActiveJob::Base.queue_adapter.enqueued_jobs.clear
80
79
  ActiveJob::Base.queue_adapter.performed_jobs.clear
81
80
 
82
- if ENV["CI"]
83
- # For unknown reasons, Wayfarer::Networking::Ferrum#renew can take a very
84
- # long time to instantiate a new ::Ferrum::Browser and requires the 60s
85
- # timeout. TODO: Figure out what's going on
81
+ if ENV.key?("CI")
86
82
  Wayfarer.config[:ferrum][:options] = { url: "http://chrome:3000", timeout: 120 }
87
- Wayfarer.config[:selenium][:driver] = :firefox
88
- Wayfarer.config[:selenium][:options] = { url: "http://firefox:4444" }
89
- Wayfarer.config[:selenium][:client_timeout] = 10
83
+
84
+ Wayfarer.config[:selenium][:driver] = :remote
85
+ Wayfarer.config[:selenium][:options] = {
86
+ url: "http://firefox:4444",
87
+ options: Selenium::WebDriver::Options.firefox
88
+ }
90
89
  end
91
90
 
92
91
  # TODO: Undo side-effect
93
92
  Capybara.register_driver(:cuprite) do |app|
94
- Capybara::Cuprite::Driver.new(app, Wayfarer.config[:ferrum][:options])
93
+ Capybara::Cuprite::Driver.new(app, Wayfarer.config.dig(:ferrum, :options))
95
94
  end
96
95
  Wayfarer.config[:capybara][:driver] = :cuprite
97
- end
98
96
 
99
- config.before(:each, redis: true) do
100
- ::Redis.new(url: redis_host).flushall
97
+ Wayfarer::Networking::Pool.finalizer = ->(_pool) {}
101
98
  end
102
99
 
103
100
  config.around(cli: true) do |example|
@@ -107,10 +104,6 @@ RSpec.configure do |config|
107
104
  example.run
108
105
  ensure
109
106
  Dir.chdir(origin)
110
- # FileUtils.rm_r(tmp)
111
- end
112
-
113
- config.before(:each, cli: true) do
114
- Object.send(:remove_const, :DummyJob) if defined?(DummyJob)
107
+ FileUtils.rm_r(tmp)
115
108
  end
116
109
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJobTestHelper
4
+ # perform_enqueued_jobs expects assert_equal to be defined
5
+ def assert_equal(expected, actual, _)
6
+ expect(actual).to eq(expected)
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IntegrationHelpers
4
+ def mock_job!(symbol)
5
+ mock!(symbol) do
6
+ Class.new(ActiveJob::Base).include(Wayfarer::Base)
7
+ end
8
+ end
9
+
10
+ def mock_handler!(symbol)
11
+ mock!(symbol) do
12
+ Class.new.include(Wayfarer::Handler)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def mock!(symbol, &block)
19
+ stub_const(symbol.to_s.camelize, block.call)
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_context "with Redis", :redis do
4
+ let(:redis) { Redis.new(url: redis_url) }
5
+
6
+ before do
7
+ redis.flushall
8
+ end
9
+ end
@@ -1,60 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class TestApp < Sinatra::Base
4
- set :root, File.dirname(__FILE__)
5
- set :public_folder, -> { File.join(root, "static") }
3
+ module Support
4
+ class TestApp < Sinatra::Base
5
+ def self.start!(port)
6
+ mutex = Mutex.new
7
+ cvar = ConditionVariable.new
6
8
 
7
- get "/status_code/:code" do
8
- status params[:code]
9
- end
9
+ Thread.new do
10
+ Rackup::Handler::WEBrick.run(
11
+ TestApp,
12
+ Host: "0.0.0.0",
13
+ Port: port,
14
+ Logger: WEBrick::Log.new(File::NULL),
15
+ AccessLog: [],
16
+ StartCallback: proc { cvar.signal }
17
+ )
18
+ end
10
19
 
11
- get "/headers/:header" do
12
- request.env[params[:header]]
13
- end
20
+ mutex.lock
21
+ cvar.wait(mutex)
22
+ end
14
23
 
15
- get "/response_header/:key/:val" do
16
- headers params[:key] => params[:val]
17
- end
24
+ set :root, File.dirname(__FILE__)
25
+ set :public_folder, -> { File.join(root, "static") }
18
26
 
19
- get "/response_header/Content-Type/*" do
20
- headers "Content-Type" => params[:splat].first
21
- end
27
+ get "/status_code/:code" do
28
+ status params[:code]
29
+ end
22
30
 
23
- get "/hello_world" do
24
- headers "hello" => "world"
25
- "Hello world!"
26
- end
31
+ get "/headers/:header" do
32
+ request.env[params[:header]]
33
+ end
27
34
 
28
- get "/body/:content" do
29
- params[:content]
30
- end
35
+ get "/response_header/:key/:val" do
36
+ headers params[:key] => params[:val]
37
+ end
31
38
 
32
- get "/redirect_loop" do
33
- redirect to "/redirect_loop"
34
- end
39
+ get "/response_header/Content-Type/*" do
40
+ content_type params[:splat].first
41
+ end
35
42
 
36
- get "/redirect" do
37
- n = params[:times].to_i
38
- n.zero? ? "You arrived!" : (redirect to "/redirect?times=#{n - 1}")
39
- end
43
+ get "/hello_world" do
44
+ headers "hello" => "world"
45
+ "Hello world!"
46
+ end
40
47
 
41
- get "/malformed_redirect" do
42
- redirect to "hptt://bro.ken"
43
- end
48
+ get "/body/:content" do
49
+ params[:content]
50
+ end
44
51
 
45
- get "/json/:file" do
46
- content_type "application/json"
47
- send_file(static_file("json/#{params[:file]}"))
48
- end
52
+ get "/redirect_loop" do
53
+ redirect to "/redirect_loop"
54
+ end
49
55
 
50
- get "/xml/:file" do
51
- content_type "application/xml"
52
- send_file(static_file("xml/#{params[:file]}"))
53
- end
56
+ get "/redirect" do
57
+ n = params[:times].to_i
58
+ n.zero? ? "You arrived!" : (redirect to "/redirect?times=#{n - 1}")
59
+ end
60
+
61
+ get "/malformed_redirect" do
62
+ redirect to "hptt://bro.ken"
63
+ end
64
+
65
+ get "/json/:file" do
66
+ content_type "application/json"
67
+ send_file(static_file("json/#{params[:file]}"))
68
+ end
69
+
70
+ get "/xml/:file" do
71
+ content_type "application/xml"
72
+ send_file(static_file("xml/#{params[:file]}"))
73
+ end
54
74
 
55
- private
75
+ private
56
76
 
57
- def static_file(file_path)
58
- File.expand_path(file_path, settings.public_folder)
77
+ def static_file(file_path)
78
+ File.expand_path(file_path, settings.public_folder)
79
+ end
59
80
  end
60
81
  end
@@ -2,17 +2,18 @@
2
2
 
3
3
  require "spec_helpers"
4
4
 
5
- describe Wayfarer::Base, redis: true do
5
+ describe Wayfarer::Base, :integration, :redis do
6
6
  let(:task) { build(:task, :redis_pool) }
7
7
 
8
8
  before do
9
- stub_const("DummyJob", Class.new(ActiveJob::Base).include(Wayfarer::Base))
9
+ mock_job! :dummy_job
10
10
  end
11
11
 
12
12
  describe "::crawl" do
13
13
  it "enqueues a task" do
14
- expect(DummyJob).to receive(:perform_later).with(task)
14
+ allow(DummyJob).to receive(:perform_later)
15
15
  DummyJob.crawl(task.url, batch: task.batch)
16
+ expect(DummyJob).to have_received(:perform_later).with(task)
16
17
  end
17
18
 
18
19
  it "returns a task" do
@@ -21,10 +22,13 @@ describe Wayfarer::Base, redis: true do
21
22
  end
22
23
 
23
24
  describe "#perform" do
24
- subject(:counter) { Wayfarer::Redis::Counter.new(task) }
25
+ let(:counter) { build(:counter, task: task) }
25
26
  let!(:initial) { counter.increment }
26
27
 
27
28
  before do
29
+ allow(Wayfarer::Redis::Counter).to receive(:new).and_return(counter)
30
+ allow(counter).to receive(:increment).and_call_original
31
+
28
32
  DummyJob.class_eval do
29
33
  route.to :index
30
34
  end
@@ -38,15 +42,14 @@ describe Wayfarer::Base, redis: true do
38
42
  end
39
43
 
40
44
  specify do
41
- expect_any_instance_of(Wayfarer::Redis::Counter)
42
- .to receive(:increment).at_least(:once).and_call_original
43
-
44
45
  expect {
45
46
  DummyJob.perform_later(task)
46
47
  perform_enqueued_jobs
47
48
  assert_performed_jobs 1
48
49
  expect(enqueued_jobs).to be_empty
49
- }.not_to change { counter.value }.from(initial)
50
+ }.not_to change(counter, :value).from(initial)
51
+
52
+ expect(counter).to have_received(:increment).at_least(:once)
50
53
  end
51
54
  end
52
55
 
@@ -60,15 +63,14 @@ describe Wayfarer::Base, redis: true do
60
63
  end
61
64
 
62
65
  specify do
63
- expect_any_instance_of(Wayfarer::Redis::Counter)
64
- .to receive(:increment).at_least(:once).and_call_original
65
-
66
66
  expect {
67
67
  DummyJob.perform_later(task)
68
68
  expect { perform_enqueued_jobs }.to raise_error(RuntimeError)
69
69
  assert_performed_jobs 1
70
70
  expect(enqueued_jobs).to be_empty
71
- }.not_to change { counter.value }.from(initial)
71
+ }.not_to change(counter, :value).from(initial)
72
+
73
+ expect(counter).to have_received(:increment).at_least(:once)
72
74
  end
73
75
  end
74
76
 
@@ -84,27 +86,25 @@ describe Wayfarer::Base, redis: true do
84
86
  end
85
87
 
86
88
  specify do
87
- expect_any_instance_of(Wayfarer::Redis::Counter)
88
- .to receive(:increment).at_least(:once).and_call_original
89
-
90
89
  expect {
91
90
  DummyJob.perform_later(task)
92
91
  2.times { perform_enqueued_jobs }
93
92
  expect { perform_enqueued_jobs }.to raise_error(RuntimeError)
94
93
  assert_performed_jobs 3
95
94
  expect(enqueued_jobs).to be_empty
96
- }.not_to change { counter.value }.from(initial)
95
+ }.not_to change(counter, :value).from(initial)
96
+
97
+ expect(counter).to have_received(:increment).at_least(:once)
97
98
  end
98
99
  end
99
100
 
100
101
  describe "discarded after retries" do
101
102
  before do
102
- DummyJob.class_eval do
103
- ErrorA = Class.new(StandardError)
104
- ErrorB = Class.new(StandardError)
103
+ stub_const("ErrorA", Class.new(StandardError))
104
+ stub_const("ErrorB", Class.new(StandardError))
105
105
 
106
+ DummyJob.class_eval do
106
107
  retry_on ErrorA, attempts: 5
107
-
108
108
  discard_on ErrorB
109
109
 
110
110
  def index
@@ -116,16 +116,15 @@ describe Wayfarer::Base, redis: true do
116
116
  end
117
117
 
118
118
  specify do
119
- expect_any_instance_of(Wayfarer::Redis::Counter)
120
- .to receive(:increment).at_least(:once).and_call_original
121
-
122
119
  expect {
123
120
  DummyJob.perform_later(task)
124
121
  4.times { perform_enqueued_jobs }
125
122
  perform_enqueued_jobs
126
123
  assert_performed_jobs 5
127
124
  expect(enqueued_jobs).to be_empty
128
- }.not_to change { counter.value }.from(initial)
125
+ }.not_to change(counter, :value).from(initial)
126
+
127
+ expect(counter).to have_received(:increment).at_least(:once)
129
128
  end
130
129
  end
131
130
 
@@ -141,15 +140,14 @@ describe Wayfarer::Base, redis: true do
141
140
  end
142
141
 
143
142
  specify do
144
- expect_any_instance_of(Wayfarer::Redis::Counter)
145
- .to receive(:increment).at_least(:once).and_call_original
146
-
147
143
  expect {
148
144
  DummyJob.perform_later(task)
149
145
  perform_enqueued_jobs
150
146
  assert_performed_jobs 1
151
147
  expect(enqueued_jobs).to be_empty
152
- }.not_to change { counter.value }.from(initial)
148
+ }.not_to change(counter, :value).from(initial)
149
+
150
+ expect(counter).to have_received(:increment).at_least(:once)
153
151
  end
154
152
  end
155
153
 
@@ -166,15 +164,14 @@ describe Wayfarer::Base, redis: true do
166
164
  end
167
165
 
168
166
  specify do
169
- expect_any_instance_of(Wayfarer::Redis::Counter)
170
- .to receive(:increment).at_least(:once).and_call_original
171
-
172
167
  expect {
173
168
  DummyJob.perform_later(task)
174
169
  expect { perform_enqueued_jobs }.to raise_error(RuntimeError)
175
170
  assert_performed_jobs 1
176
171
  expect(enqueued_jobs).to be_empty
177
- }.not_to change { counter.value }.from(initial)
172
+ }.not_to change(counter, :value).from(initial)
173
+
174
+ expect(counter).to have_received(:increment).at_least(:once)
178
175
  end
179
176
  end
180
177
 
@@ -188,15 +185,14 @@ describe Wayfarer::Base, redis: true do
188
185
  end
189
186
 
190
187
  specify do
191
- expect_any_instance_of(Wayfarer::Redis::Counter)
192
- .to receive(:increment).at_least(:once).and_call_original
193
-
194
188
  expect {
195
189
  DummyJob.perform_later(task)
196
190
  expect { perform_enqueued_jobs }.to raise_error(RuntimeError)
197
191
  assert_performed_jobs 1
198
192
  expect(enqueued_jobs).to be_empty
199
- }.not_to change { counter.value }.from(initial)
193
+ }.not_to change(counter, :value).from(initial)
194
+
195
+ expect(counter).to have_received(:increment).at_least(:once)
200
196
  end
201
197
  end
202
198
  end