evil-client 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +7 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +98 -0
  6. data/.travis.yml +17 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +144 -0
  10. data/Rakefile +6 -0
  11. data/docs/base_url.md +38 -0
  12. data/docs/documentation.md +9 -0
  13. data/docs/headers.md +59 -0
  14. data/docs/http_method.md +31 -0
  15. data/docs/index.md +127 -0
  16. data/docs/license.md +19 -0
  17. data/docs/model.md +173 -0
  18. data/docs/operation.md +0 -0
  19. data/docs/overview.md +0 -0
  20. data/docs/path.md +48 -0
  21. data/docs/query.md +99 -0
  22. data/docs/responses.md +66 -0
  23. data/docs/security.md +102 -0
  24. data/docs/settings.md +32 -0
  25. data/evil-client.gemspec +25 -0
  26. data/lib/evil/client.rb +97 -0
  27. data/lib/evil/client/connection.rb +35 -0
  28. data/lib/evil/client/connection/net_http.rb +57 -0
  29. data/lib/evil/client/dsl.rb +110 -0
  30. data/lib/evil/client/dsl/files.rb +37 -0
  31. data/lib/evil/client/dsl/operation.rb +102 -0
  32. data/lib/evil/client/dsl/operations.rb +41 -0
  33. data/lib/evil/client/dsl/scope.rb +34 -0
  34. data/lib/evil/client/dsl/security.rb +57 -0
  35. data/lib/evil/client/middleware.rb +81 -0
  36. data/lib/evil/client/middleware/base.rb +15 -0
  37. data/lib/evil/client/middleware/merge_security.rb +16 -0
  38. data/lib/evil/client/middleware/normalize_headers.rb +13 -0
  39. data/lib/evil/client/middleware/stringify_form.rb +36 -0
  40. data/lib/evil/client/middleware/stringify_json.rb +15 -0
  41. data/lib/evil/client/middleware/stringify_multipart.rb +32 -0
  42. data/lib/evil/client/middleware/stringify_multipart/part.rb +36 -0
  43. data/lib/evil/client/middleware/stringify_query.rb +31 -0
  44. data/lib/evil/client/model.rb +65 -0
  45. data/lib/evil/client/operation.rb +34 -0
  46. data/lib/evil/client/operation/request.rb +42 -0
  47. data/lib/evil/client/operation/response.rb +40 -0
  48. data/lib/evil/client/operation/response_error.rb +12 -0
  49. data/lib/evil/client/operation/unexpected_response_error.rb +16 -0
  50. data/mkdocs.yml +21 -0
  51. data/spec/features/instantiation_spec.rb +68 -0
  52. data/spec/features/middleware_spec.rb +75 -0
  53. data/spec/features/operation_with_documentation_spec.rb +41 -0
  54. data/spec/features/operation_with_files_spec.rb +40 -0
  55. data/spec/features/operation_with_form_body_spec.rb +158 -0
  56. data/spec/features/operation_with_headers_spec.rb +99 -0
  57. data/spec/features/operation_with_http_method_spec.rb +45 -0
  58. data/spec/features/operation_with_json_body_spec.rb +156 -0
  59. data/spec/features/operation_with_path_spec.rb +47 -0
  60. data/spec/features/operation_with_query_spec.rb +84 -0
  61. data/spec/features/operation_with_response_spec.rb +109 -0
  62. data/spec/features/operation_with_security_spec.rb +228 -0
  63. data/spec/features/scoping_spec.rb +48 -0
  64. data/spec/spec_helper.rb +23 -0
  65. data/spec/support/test_client.rb +15 -0
  66. data/spec/unit/evil/client/connection/net_http_spec.rb +38 -0
  67. data/spec/unit/evil/client/dsl/files_spec.rb +37 -0
  68. data/spec/unit/evil/client/dsl/operation_spec.rb +233 -0
  69. data/spec/unit/evil/client/dsl/operations_spec.rb +27 -0
  70. data/spec/unit/evil/client/dsl/scope_spec.rb +30 -0
  71. data/spec/unit/evil/client/dsl/security_spec.rb +135 -0
  72. data/spec/unit/evil/client/dsl_spec.rb +57 -0
  73. data/spec/unit/evil/client/middleware/merge_security_spec.rb +32 -0
  74. data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +17 -0
  75. data/spec/unit/evil/client/middleware/stringify_form_spec.rb +63 -0
  76. data/spec/unit/evil/client/middleware/stringify_json_spec.rb +61 -0
  77. data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +59 -0
  78. data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +62 -0
  79. data/spec/unit/evil/client/middleware/stringify_query_spec.rb +40 -0
  80. data/spec/unit/evil/client/middleware_spec.rb +46 -0
  81. data/spec/unit/evil/client/model_spec.rb +100 -0
  82. data/spec/unit/evil/client/operation/request_spec.rb +49 -0
  83. data/spec/unit/evil/client/operation/response_spec.rb +61 -0
  84. metadata +271 -0
@@ -0,0 +1,47 @@
1
+ RSpec.describe "operation with path" do
2
+ # see Test::Client definition in `/spec/support/test_client.rb`
3
+ before do
4
+ class Test::Client < Evil::Client
5
+ operation do
6
+ http_method :get
7
+ path { "users" }
8
+ response 200
9
+ end
10
+
11
+ operation :find_users
12
+
13
+ operation :find_user do
14
+ path { |id:, **| "users/#{id}" }
15
+ end
16
+
17
+ operation :login do |settings|
18
+ path { "login/#{settings.token}" }
19
+ end
20
+ end
21
+
22
+ stub_request :get, //
23
+ end
24
+
25
+ let(:client) { Test::Client.new "foo", user: "bar", version: 3, token: "baz" }
26
+
27
+ it "uses default path" do
28
+ client.operations[:find_users].call
29
+
30
+ expect(a_request(:get, "https://foo.example.com/api/v3/users"))
31
+ .to have_been_made
32
+ end
33
+
34
+ it "uses request options" do
35
+ client.operations[:find_user].call(id: 42)
36
+
37
+ expect(a_request(:get, "https://foo.example.com/api/v3/users/42"))
38
+ .to have_been_made
39
+ end
40
+
41
+ it "uses settings" do
42
+ client.operations[:login].call
43
+
44
+ expect(a_request(:get, "https://foo.example.com/api/v3/login/baz"))
45
+ .to have_been_made
46
+ end
47
+ end
@@ -0,0 +1,84 @@
1
+ RSpec.describe "operation with query" do
2
+ # see Test::Client definition in `/spec/support/test_client.rb`
3
+ before do
4
+ class Test::User < Evil::Client::Model
5
+ attribute :name
6
+ end
7
+
8
+ class Test::Client < Evil::Client
9
+ operation do
10
+ http_method :get
11
+ path { "users" }
12
+ response 200
13
+ end
14
+
15
+ operation :filter do
16
+ query do
17
+ attribute :age, type: Dry::Types["coercible.int"]
18
+ attribute :male, default: proc { true }
19
+ attribute :name, optional: true
20
+ end
21
+ end
22
+
23
+ operation :search do
24
+ query model: Test::User
25
+ end
26
+ end
27
+
28
+ stub_request(:get, //)
29
+ end
30
+
31
+ let(:path) { "https://foo.example.com/api/v3/users" }
32
+ let(:client) { Test::Client.new "foo", user: "bar", version: 3, token: "baz" }
33
+
34
+ it "provides a query from options ordered by name" do
35
+ client.operations[:filter].call age: 48, male: false, name: "John"
36
+
37
+ expect(a_request(:get, "#{path}?age=48&male=false&name=John"))
38
+ .to have_been_made
39
+ end
40
+
41
+ it "uses http encoding" do
42
+ client.operations[:filter].call age: 7, name: "Ян"
43
+
44
+ expect(a_request(:get, "#{path}?age=7&male=true&name=%D0%AF%D0%BD"))
45
+ .to have_been_made
46
+ end
47
+
48
+ it "skips unassigned optional attributes" do
49
+ client.operations[:filter].call age: 7
50
+
51
+ expect(a_request(:get, "#{path}?age=7&male=true")).to have_been_made
52
+ end
53
+
54
+ it "accepts a model" do
55
+ client.operations[:search].call name: "Joe"
56
+
57
+ expect(a_request(:get, "#{path}?name=Joe")).to have_been_made
58
+ end
59
+
60
+ it "ignores unspecified options" do
61
+ client.operations[:search].call id: 1, name: "Joe"
62
+
63
+ expect(a_request(:get, "#{path}?name=Joe")).to have_been_made
64
+ end
65
+
66
+ it "applies type restrictuions" do
67
+ expect { client.operations[:filter].call id: 1, name: "Joe" }
68
+ .to raise_error(ArgumentError)
69
+ end
70
+
71
+ it "supports nesting in a Rails style" do
72
+ client.operations[:search].call name: {
73
+ "first": "John", last: "Doe", middle: %w(Juan Andre)
74
+ }
75
+ query = [
76
+ "name%5Bfirst%5D=John",
77
+ "name%5Blast%5D=Doe",
78
+ "name%5Bmiddle%5D%5B0%5D=Juan",
79
+ "name%5Bmiddle%5D%5B1%5D=Andre"
80
+ ].join("&")
81
+
82
+ expect(a_request(:get, "#{path}?#{query}")).to have_been_made
83
+ end
84
+ end
@@ -0,0 +1,109 @@
1
+ RSpec.describe "operation with query" do
2
+ # see Test::Client definition in `/spec/support/test_client.rb`
3
+ before do
4
+ class Test::User < Evil::Client::Model
5
+ attribute :name
6
+ end
7
+
8
+ class Test::Client < Evil::Client
9
+ operation do
10
+ http_method :get
11
+ path { "users" }
12
+
13
+ response 200
14
+ end
15
+
16
+ operation :example do
17
+ response 201 do |body:, header:, response:|
18
+ [body, header, response]
19
+ end
20
+
21
+ response 404, raise: true
22
+
23
+ response 422, raise: true do |body:, header:, response:|
24
+ [body, header, response]
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ let(:client) { Test::Client.new "foo", user: "bar", version: 3, token: "baz" }
31
+
32
+ subject { client.operations[:example].call }
33
+
34
+ it "takes default setting" do
35
+ stub_request(:get, //).to_return status: 200,
36
+ headers: { "Foo" => "BAR" },
37
+ body: "Hi!"
38
+
39
+ expect(subject).to be_kind_of Rack::Response
40
+ expect(subject.headers).to include "foo" => ["BAR"]
41
+ expect(subject.body).to eq ["Hi!"]
42
+ end
43
+
44
+ it "applies block to coerce data" do
45
+ stub_request(:get, //).to_return status: 201,
46
+ headers: { "Foo" => "BAR" },
47
+ body: "Hi!"
48
+
49
+ body, headers, response = subject
50
+
51
+ expect(response.headers).to include "foo" => ["BAR"]
52
+ expect(response.body).to eq ["Hi!"]
53
+
54
+ expect(body).to eq response.body
55
+ expect(headers).to eq response.headers
56
+ end
57
+
58
+ it "raises ResponseError when necessary" do
59
+ stub_request(:get, //).to_return status: 404,
60
+ headers: { "Foo" => "BAR" },
61
+ body: "Hi!"
62
+
63
+ begin
64
+ subject
65
+ rescue Evil::Client::Operation::ResponseError => error
66
+ expect(error.response).to be_kind_of Rack::Response
67
+ expect(error.response.headers).to include "foo" => ["BAR"]
68
+ expect(error.response.body).to eq ["Hi!"]
69
+ else
70
+ fail
71
+ end
72
+ end
73
+
74
+ it "can raise ResponseError with coercion" do
75
+ stub_request(:get, //).to_return status: 422,
76
+ headers: { "Foo" => "BAR" },
77
+ body: "Hi!"
78
+
79
+ begin
80
+ subject
81
+ rescue Evil::Client::Operation::ResponseError => error
82
+ body, headers, response = error.response
83
+
84
+ expect(response.headers).to include "foo" => ["BAR"]
85
+ expect(response.body).to eq ["Hi!"]
86
+
87
+ expect(body).to eq response.body
88
+ expect(headers).to eq response.headers
89
+ else
90
+ fail
91
+ end
92
+ end
93
+
94
+ it "raises UnexpectedResponseError when a status not expected" do
95
+ stub_request(:get, //).to_return status: 400,
96
+ headers: { "Foo" => "BAR" },
97
+ body: "Hi!"
98
+
99
+ begin
100
+ subject
101
+ rescue Evil::Client::Operation::UnexpectedResponseError => error
102
+ expect(error.response).to be_kind_of Rack::Response
103
+ expect(error.response.headers).to include "foo" => ["BAR"]
104
+ expect(error.response.body).to eq ["Hi!"]
105
+ else
106
+ fail
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,228 @@
1
+ RSpec.describe "operation with security" do
2
+ # see Test::Client definition in `/spec/support/test_client.rb`
3
+ before do
4
+ class Test::Client < Evil::Client
5
+ operation do
6
+ http_method :get
7
+ path { "data" }
8
+ response 200
9
+ end
10
+ end
11
+
12
+ stub_request(:get, //)
13
+ end
14
+
15
+ let(:path) { "https://foo.example.com/api/v3/data" }
16
+ let(:client) do
17
+ Test::Client.new "foo",
18
+ version: 3,
19
+ user: "bar",
20
+ password: "baz",
21
+ token: "qux"
22
+ end
23
+
24
+ subject { client.operations[:example].call }
25
+
26
+ context "without operation-specific settings" do
27
+ before do
28
+ class Test::Client < Evil::Client
29
+ operation do |settings|
30
+ http_method :get
31
+ path { "data" }
32
+ response 200
33
+
34
+ security do
35
+ basic_auth settings.user, settings.password
36
+ end
37
+ end
38
+
39
+ operation :example
40
+ end
41
+ end
42
+
43
+ it "uses default settings" do
44
+ request = a_request(:get, path).with do |req|
45
+ expect(req.headers).to include "Authorization" => "Basic YmFyOmJheg=="
46
+ end
47
+
48
+ subject
49
+ expect(request).to have_been_made
50
+ end
51
+ end
52
+
53
+ context "with basic_auth" do
54
+ before do
55
+ class Test::Client < Evil::Client
56
+ operation :example do |settings|
57
+ security { basic_auth settings.user, settings.password }
58
+ end
59
+ end
60
+ end
61
+
62
+ it "adds header" do
63
+ request = a_request(:get, path).with do |req|
64
+ expect(req.headers).to include "Authorization" => "Basic YmFyOmJheg=="
65
+ end
66
+
67
+ subject
68
+ expect(request).to have_been_made
69
+ end
70
+ end
71
+
72
+ context "with prefixed token_auth in headers" do
73
+ before do
74
+ class Test::Client < Evil::Client
75
+ operation :example do |settings|
76
+ security { token_auth settings.token, prefix: "Bearer" }
77
+ end
78
+ end
79
+ end
80
+
81
+ it "adds header" do
82
+ request = a_request(:get, path).with do |req|
83
+ expect(req.headers).to include "Authorization" => "Bearer qux"
84
+ end
85
+
86
+ subject
87
+ expect(request).to have_been_made
88
+ end
89
+ end
90
+
91
+ context "with token_auth without prefix in headers" do
92
+ before do
93
+ class Test::Client < Evil::Client
94
+ operation :example do |settings|
95
+ security { token_auth settings.token }
96
+ end
97
+ end
98
+ end
99
+
100
+ it "adds header" do
101
+ request = a_request(:get, path).with do |req|
102
+ expect(req.headers).to include "Authorization" => "qux"
103
+ end
104
+
105
+ subject
106
+ expect(request).to have_been_made
107
+ end
108
+ end
109
+
110
+ context "with token_auth in query" do
111
+ before do
112
+ class Test::Client < Evil::Client
113
+ operation :example do |settings|
114
+ security { token_auth settings.token, using: :query }
115
+ end
116
+ end
117
+ end
118
+
119
+ it "adds options to query" do
120
+ request = a_request(:get, "#{path}?access_token=qux").with do |req|
121
+ expect(req.headers.keys).not_to include "Authorization"
122
+ end
123
+
124
+ subject
125
+ expect(request).to have_been_made
126
+ end
127
+ end
128
+
129
+ context "with token_auth in json body" do
130
+ before do
131
+ class Test::Client < Evil::Client
132
+ operation :example do |settings|
133
+ security { token_auth settings.token, using: :body }
134
+ body format: "json"
135
+ end
136
+ end
137
+ end
138
+
139
+ it "adds options to body" do
140
+ request = a_request(:get, path).with do |req|
141
+ expect(req.body).to eq '{"access_token":"qux"}'
142
+ end
143
+
144
+ subject
145
+ expect(request).to have_been_made
146
+ end
147
+ end
148
+
149
+ context "with token_auth in a plain(form) body" do
150
+ before do
151
+ class Test::Client < Evil::Client
152
+ operation :example do |settings|
153
+ security { token_auth settings.token, using: :body }
154
+ body format: "form"
155
+ end
156
+ end
157
+ end
158
+
159
+ it "adds options to body" do
160
+ request = a_request(:get, path).with do |req|
161
+ expect(req.body).to eq "access_token=qux"
162
+ end
163
+
164
+ subject
165
+ expect(request).to have_been_made
166
+ end
167
+ end
168
+
169
+ context "with key_auth in headers" do
170
+ before do
171
+ class Test::Client < Evil::Client
172
+ operation :example do
173
+ security { key_auth "fOo", "bAr" }
174
+ end
175
+ end
176
+ end
177
+
178
+ it "adds header" do
179
+ request = a_request(:get, path).with do |req|
180
+ expect(req.headers).to include "Foo" => "bAr"
181
+ end
182
+
183
+ subject
184
+ expect(request).to have_been_made
185
+ end
186
+ end
187
+
188
+ context "with key_auth in query" do
189
+ before do
190
+ class Test::Client < Evil::Client
191
+ operation :example do |_settings|
192
+ security { key_auth :fOo, :bAr, using: :query }
193
+ end
194
+ end
195
+ end
196
+
197
+ it "adds options to query" do
198
+ request = a_request(:get, "#{path}?fOo=bAr")
199
+
200
+ subject
201
+ expect(request).to have_been_made
202
+ end
203
+ end
204
+
205
+ context "with several methods at once" do
206
+ before do
207
+ class Test::Client < Evil::Client
208
+ operation :example do |_settings|
209
+ security do
210
+ key_auth "foo", "bar", using: :query
211
+ key_auth "baz", "qux"
212
+ basic_auth "foo", "bar"
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ it "combines definitions" do
219
+ request = a_request(:get, "#{path}?foo=bar").with do |req|
220
+ expect(req.headers).to include "Authorization" => "Basic Zm9vOmJhcg==",
221
+ "Baz" => "qux"
222
+ end
223
+
224
+ subject
225
+ expect(request).to have_been_made
226
+ end
227
+ end
228
+ end