coach 0.5.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|