coach 0.5.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +96 -0
- data/.rubocop.yml +16 -113
- data/.rubocop_todo.yml +27 -0
- data/CHANGELOG.md +21 -1
- data/Gemfile +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +30 -6
- data/coach.gemspec +13 -12
- data/lib/coach.rb +13 -13
- data/lib/coach/handler.rb +32 -6
- data/lib/coach/middleware.rb +33 -6
- data/lib/coach/middleware_item.rb +2 -0
- data/lib/coach/notifications.rb +21 -13
- data/lib/coach/request_benchmark.rb +3 -3
- data/lib/coach/request_serializer.rb +3 -3
- data/lib/coach/router.rb +5 -7
- data/lib/coach/rspec.rb +10 -6
- data/lib/coach/version.rb +1 -1
- data/spec/lib/coach/handler_spec.rb +118 -38
- data/spec/lib/coach/middleware_spec.rb +5 -1
- data/spec/lib/coach/middleware_validator_spec.rb +4 -4
- data/spec/lib/coach/notifications_spec.rb +22 -21
- data/spec/lib/coach/request_benchmark_spec.rb +7 -7
- data/spec/lib/coach/request_serializer_spec.rb +15 -15
- data/spec/lib/coach/router_spec.rb +21 -17
- data/spec/spec_helper.rb +4 -4
- metadata +18 -17
- data/.travis.yml +0 -22
@@ -1,7 +1,7 @@
|
|
1
1
|
require "coach/middleware"
|
2
2
|
|
3
3
|
describe Coach::Middleware do
|
4
|
-
let(:middleware_class) { Class.new(
|
4
|
+
let(:middleware_class) { Class.new(described_class) }
|
5
5
|
let(:context_) { {} }
|
6
6
|
let(:middleware_obj) { middleware_class.new(context_, nil) }
|
7
7
|
|
@@ -17,8 +17,10 @@ describe Coach::Middleware do
|
|
17
17
|
before { middleware_class.provides(:foo, :bar) }
|
18
18
|
|
19
19
|
it "returns true" do
|
20
|
+
# rubocop:disable RSpec/PredicateMatcher
|
20
21
|
expect(middleware_class.provides?(:foo)).to be_truthy
|
21
22
|
expect(middleware_class.provides?(:bar)).to be_truthy
|
23
|
+
# rubocop:enable RSpec/PredicateMatcher
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
@@ -26,7 +28,9 @@ describe Coach::Middleware do
|
|
26
28
|
before { middleware_class.provides(:foo) }
|
27
29
|
|
28
30
|
it "returns false" do
|
31
|
+
# rubocop:disable RSpec/PredicateMatcher
|
29
32
|
expect(middleware_class.provides?(:baz)).to be_falsy
|
33
|
+
# rubocop:enable RSpec/PredicateMatcher
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -4,12 +4,12 @@ require "coach/middleware_validator"
|
|
4
4
|
describe Coach::MiddlewareValidator do
|
5
5
|
subject(:validator) { described_class.new(head_middleware, already_provided) }
|
6
6
|
|
7
|
-
let(:head_middleware) { build_middleware(
|
7
|
+
let(:head_middleware) { build_middleware("Head") }
|
8
8
|
let(:already_provided) { [] }
|
9
9
|
|
10
|
-
let(:middleware_a) { build_middleware(
|
11
|
-
let(:middleware_b) { build_middleware(
|
12
|
-
let(:middleware_c) { build_middleware(
|
10
|
+
let(:middleware_a) { build_middleware("A") }
|
11
|
+
let(:middleware_b) { build_middleware("B") }
|
12
|
+
let(:middleware_c) { build_middleware("C") }
|
13
13
|
|
14
14
|
# head <── a
|
15
15
|
# └─ b <- c
|
@@ -1,36 +1,37 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require "coach/notifications"
|
3
3
|
|
4
4
|
describe Coach::Notifications do
|
5
5
|
subject(:notifications) { described_class.instance }
|
6
|
-
before { described_class.unsubscribe! }
|
7
6
|
|
8
|
-
# Remove need to fully mock a request object
|
9
7
|
before do
|
8
|
+
described_class.unsubscribe!
|
9
|
+
|
10
|
+
# Remove need to fully mock a request object
|
10
11
|
allow(Coach::RequestSerializer).
|
11
|
-
to receive(:new).
|
12
|
+
to receive(:new).
|
13
|
+
and_return(instance_double("Coach::RequestSerializer", serialize: {}))
|
14
|
+
|
15
|
+
ActiveSupport::Notifications.subscribe(/\.coach$/) do |name, *_, event|
|
16
|
+
events << [name, event]
|
17
|
+
end
|
12
18
|
end
|
13
19
|
|
14
20
|
# Capture all Coach events
|
15
21
|
let(:events) { [] }
|
16
22
|
let(:middleware_event) do
|
17
|
-
event = events.find { |(name, _)| name ==
|
23
|
+
event = events.find { |(name, _)| name == "request.coach" }
|
18
24
|
event && event[1]
|
19
25
|
end
|
20
|
-
before do
|
21
|
-
ActiveSupport::Notifications.subscribe(/coach/) do |name, *_, event|
|
22
|
-
events << [name, event]
|
23
|
-
end
|
24
|
-
end
|
25
26
|
|
26
27
|
# Mock a handler to simulate an endpoint call
|
27
28
|
let(:handler) do
|
28
|
-
middleware_a = build_middleware(
|
29
|
-
middleware_b = build_middleware(
|
29
|
+
middleware_a = build_middleware("A")
|
30
|
+
middleware_b = build_middleware("B")
|
30
31
|
|
31
32
|
middleware_a.uses(middleware_b)
|
32
33
|
|
33
|
-
terminal_middleware = build_middleware(
|
34
|
+
terminal_middleware = build_middleware("Terminal")
|
34
35
|
terminal_middleware.uses(middleware_a)
|
35
36
|
|
36
37
|
Coach::Handler.new(terminal_middleware)
|
@@ -43,17 +44,17 @@ describe Coach::Notifications do
|
|
43
44
|
expect(notifications.active?).to be(true)
|
44
45
|
end
|
45
46
|
|
46
|
-
it "will now send coach
|
47
|
+
it "will now send request.coach" do
|
47
48
|
handler.call({})
|
48
|
-
expect(middleware_event).
|
49
|
+
expect(middleware_event).to_not be_nil
|
49
50
|
end
|
50
51
|
|
51
|
-
describe "coach
|
52
|
+
describe "request.coach event" do
|
52
53
|
before { handler.call({}) }
|
53
54
|
|
54
55
|
it "contains all middleware that have been run" do
|
55
56
|
middleware_names = middleware_event[:chain].map { |item| item[:name] }
|
56
|
-
expect(middleware_names).to include(
|
57
|
+
expect(middleware_names).to include("Terminal", "A", "B")
|
57
58
|
end
|
58
59
|
|
59
60
|
it "includes all logged metadata" do
|
@@ -64,17 +65,17 @@ describe Coach::Notifications do
|
|
64
65
|
end
|
65
66
|
|
66
67
|
describe "#unsubscribe!" do
|
67
|
-
it "
|
68
|
+
it "disables any prior subscriptions" do
|
68
69
|
notifications.subscribe!
|
69
70
|
|
70
71
|
handler.call({})
|
71
|
-
expect(events.count { |(name, _)| name ==
|
72
|
+
expect(events.count { |(name, _)| name == "request.coach" }).
|
72
73
|
to eq(1)
|
73
74
|
|
74
75
|
notifications.unsubscribe!
|
75
76
|
|
76
77
|
handler.call({})
|
77
|
-
expect(events.count { |(name, _)| name ==
|
78
|
+
expect(events.count { |(name, _)| name == "request.coach" }).
|
78
79
|
to eq(1)
|
79
80
|
end
|
80
81
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require "coach/request_benchmark"
|
3
3
|
|
4
4
|
describe Coach::RequestBenchmark do
|
5
|
-
subject(:event) { described_class.new(
|
5
|
+
subject(:event) { described_class.new("ENDPOINT") }
|
6
6
|
|
7
7
|
let(:base_time) { Time.now }
|
8
8
|
|
@@ -14,8 +14,8 @@ describe Coach::RequestBenchmark do
|
|
14
14
|
let(:finish) { base_time + 5 }
|
15
15
|
|
16
16
|
before do
|
17
|
-
event.notify(
|
18
|
-
event.notify(
|
17
|
+
event.notify("B", b_start, b_finish)
|
18
|
+
event.notify("A", a_start, a_finish)
|
19
19
|
event.complete(start, finish)
|
20
20
|
end
|
21
21
|
|
@@ -35,11 +35,11 @@ describe Coach::RequestBenchmark do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
it "computes duration of middleware with no children" do
|
38
|
-
expect(stats[:chain]).to include(name:
|
38
|
+
expect(stats[:chain]).to include(name: "B", duration: 1000)
|
39
39
|
end
|
40
40
|
|
41
41
|
it "adjusts duration of middleware for their children" do
|
42
|
-
expect(stats[:chain]).to include(name:
|
42
|
+
expect(stats[:chain]).to include(name: "A", duration: 2000)
|
43
43
|
end
|
44
44
|
|
45
45
|
it "correctly orders chain" do
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require "active_support/core_ext/object/try"
|
3
|
+
require "coach/request_serializer"
|
4
4
|
|
5
5
|
describe Coach::RequestSerializer do
|
6
6
|
describe ".apply_header_rule" do
|
7
7
|
before { described_class.clear_header_rules! }
|
8
8
|
|
9
|
-
let(:header) {
|
9
|
+
let(:header) { "http_abc" }
|
10
10
|
let(:rule) { nil }
|
11
11
|
|
12
12
|
context "with header that has a rule that" do
|
@@ -14,8 +14,8 @@ describe Coach::RequestSerializer do
|
|
14
14
|
|
15
15
|
context "does not specify block" do
|
16
16
|
it "replaces blacklisted header with default text" do
|
17
|
-
sanitized =
|
18
|
-
expect(sanitized).
|
17
|
+
sanitized = described_class.apply_header_rule(header, "value")
|
18
|
+
expect(sanitized).to_not eq("value")
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -23,20 +23,22 @@ describe Coach::RequestSerializer do
|
|
23
23
|
let(:rule) { ->(value) { "#{value}#{value}" } }
|
24
24
|
|
25
25
|
it "uses block to compute new filtered value" do
|
26
|
-
sanitized =
|
27
|
-
expect(sanitized).to eq(
|
26
|
+
sanitized = described_class.apply_header_rule(header, "value")
|
27
|
+
expect(sanitized).to eq("valuevalue")
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
context "with header that has no blacklist rule" do
|
33
33
|
it "does not modify value" do
|
34
|
-
sanitized =
|
35
|
-
expect(sanitized).to eq(
|
34
|
+
sanitized = described_class.apply_header_rule(header, "value")
|
35
|
+
expect(sanitized).to eq("value")
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
context "as an instance" do
|
40
|
+
subject(:request_serializer) { described_class.new(mock_request) }
|
41
|
+
|
40
42
|
let(:mock_request) do
|
41
43
|
instance_double("ActionDispatch::Request", format: nil,
|
42
44
|
remote_ip: nil,
|
@@ -45,25 +47,23 @@ describe Coach::RequestSerializer do
|
|
45
47
|
filtered_parameters: nil,
|
46
48
|
filtered_env: {
|
47
49
|
"foo" => "bar",
|
48
|
-
"HTTP_foo" => "bar"
|
50
|
+
"HTTP_foo" => "bar",
|
49
51
|
})
|
50
52
|
end
|
51
53
|
|
52
|
-
subject(:request_serializer) { described_class.new(mock_request) }
|
53
|
-
|
54
54
|
describe "#serialize" do
|
55
55
|
subject(:serialized) { request_serializer.serialize }
|
56
56
|
|
57
57
|
it "rescues any exceptions request#fullpath may raise" do
|
58
58
|
allow(mock_request).to receive(:fullpath).and_raise
|
59
59
|
|
60
|
-
expect(serialized[:path]).to eq(
|
60
|
+
expect(serialized[:path]).to eq("unknown")
|
61
61
|
end
|
62
62
|
|
63
63
|
it "filters headers allowing only those prefixed with 'HTTP_'" do
|
64
64
|
allow(mock_request).to receive(:fullpath).and_return(nil)
|
65
65
|
|
66
|
-
expect(serialized[:headers]).
|
66
|
+
expect(serialized[:headers]).to_not include("foo")
|
67
67
|
expect(serialized[:headers]).to include("http_foo")
|
68
68
|
end
|
69
69
|
end
|
@@ -1,15 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "coach/router"
|
4
|
+
require "coach/handler"
|
5
5
|
|
6
6
|
describe Coach::Router do
|
7
7
|
subject(:router) { described_class.new(mapper) }
|
8
|
-
let(:mapper) { double(:mapper) }
|
9
8
|
|
10
|
-
|
11
|
-
allow(Coach::Handler).to receive(:new) { |route| route }
|
12
|
-
end
|
9
|
+
let(:mapper) { instance_double("ActionDispatch::Routing::Mapper") }
|
13
10
|
|
14
11
|
let(:resource_routes) do
|
15
12
|
routes_module = Module.new
|
@@ -19,6 +16,10 @@ describe Coach::Router do
|
|
19
16
|
routes_module
|
20
17
|
end
|
21
18
|
|
19
|
+
before do
|
20
|
+
allow(Coach::Handler).to receive(:new) { |route| route }
|
21
|
+
end
|
22
|
+
|
22
23
|
shared_examples "mount action" do |action, params|
|
23
24
|
let(:actions) { [action] }
|
24
25
|
|
@@ -35,31 +36,33 @@ describe Coach::Router do
|
|
35
36
|
end
|
36
37
|
|
37
38
|
describe "#draw" do
|
38
|
-
subject(:draw) { router.draw(resource_routes, base:
|
39
|
+
subject(:draw) { router.draw(resource_routes, base: "/resource", actions: actions) }
|
39
40
|
|
40
41
|
context "with default action" do
|
41
|
-
it_behaves_like "mount action", :index, url:
|
42
|
-
it_behaves_like "mount action", :show, url:
|
43
|
-
it_behaves_like "mount action", :create, url:
|
44
|
-
it_behaves_like "mount action", :update, url:
|
45
|
-
it_behaves_like "mount action", :destroy, url:
|
42
|
+
it_behaves_like "mount action", :index, url: "/resource", method: :get
|
43
|
+
it_behaves_like "mount action", :show, url: "/resource/:id", method: :get
|
44
|
+
it_behaves_like "mount action", :create, url: "/resource", method: :post
|
45
|
+
it_behaves_like "mount action", :update, url: "/resource/:id", method: :put
|
46
|
+
it_behaves_like "mount action", :destroy, url: "/resource/:id", method: :delete
|
46
47
|
end
|
47
48
|
|
48
49
|
context "with custom action" do
|
49
50
|
let(:actions) { [refund: { method: :post, url: custom_url }] }
|
50
51
|
|
51
52
|
context "with no slash" do
|
52
|
-
let(:custom_url) {
|
53
|
+
let(:custom_url) { ":id/refund" }
|
54
|
+
|
53
55
|
it "mounts correct url" do
|
54
|
-
expect(mapper).to receive(:match).with(
|
56
|
+
expect(mapper).to receive(:match).with("/resource/:id/refund", anything)
|
55
57
|
draw
|
56
58
|
end
|
57
59
|
end
|
58
60
|
|
59
61
|
context "with multiple /'s" do
|
60
|
-
let(:custom_url) {
|
62
|
+
let(:custom_url) { "//:id/refund" }
|
63
|
+
|
61
64
|
it "mounts correct url" do
|
62
|
-
expect(mapper).to receive(:match).with(
|
65
|
+
expect(mapper).to receive(:match).with("/resource/:id/refund", anything)
|
63
66
|
draw
|
64
67
|
end
|
65
68
|
end
|
@@ -67,6 +70,7 @@ describe Coach::Router do
|
|
67
70
|
|
68
71
|
context "with unknown default action" do
|
69
72
|
let(:actions) { [:unknown] }
|
73
|
+
|
70
74
|
it "raises RouterUnknownDefaultAction" do
|
71
75
|
expect { draw }.to raise_error(Coach::Errors::RouterUnknownDefaultAction)
|
72
76
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coach
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GoCardless
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -39,61 +39,61 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: pry
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0.10'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.2'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.2'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rspec-its
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '1.2'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '1.2'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rspec_junit_formatter
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.
|
89
|
+
version: 0.3.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.
|
96
|
+
version: 0.3.0
|
97
97
|
description:
|
98
98
|
email:
|
99
99
|
- developers@gocardless.com
|
@@ -101,10 +101,11 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
+
- ".circleci/config.yml"
|
104
105
|
- ".gitignore"
|
105
106
|
- ".rspec"
|
106
107
|
- ".rubocop.yml"
|
107
|
-
- ".
|
108
|
+
- ".rubocop_todo.yml"
|
108
109
|
- CHANGELOG.md
|
109
110
|
- Gemfile
|
110
111
|
- LICENSE.txt
|
@@ -142,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
143
|
requirements:
|
143
144
|
- - ">="
|
144
145
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
146
|
+
version: '2.2'
|
146
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
148
|
requirements:
|
148
149
|
- - ">="
|
@@ -150,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
151
|
version: '0'
|
151
152
|
requirements: []
|
152
153
|
rubyforge_project:
|
153
|
-
rubygems_version: 2.
|
154
|
+
rubygems_version: 2.7.4
|
154
155
|
signing_key:
|
155
156
|
specification_version: 4
|
156
157
|
summary: Alternative controllers built with middleware
|