evil-client 0.2.1
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 +7 -0
- data/.codeclimate.yml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +98 -0
- data/.travis.yml +17 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +144 -0
- data/Rakefile +6 -0
- data/docs/base_url.md +38 -0
- data/docs/documentation.md +9 -0
- data/docs/headers.md +59 -0
- data/docs/http_method.md +31 -0
- data/docs/index.md +127 -0
- data/docs/license.md +19 -0
- data/docs/model.md +173 -0
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +48 -0
- data/docs/query.md +99 -0
- data/docs/responses.md +66 -0
- data/docs/security.md +102 -0
- data/docs/settings.md +32 -0
- data/evil-client.gemspec +25 -0
- data/lib/evil/client.rb +97 -0
- data/lib/evil/client/connection.rb +35 -0
- data/lib/evil/client/connection/net_http.rb +57 -0
- data/lib/evil/client/dsl.rb +110 -0
- data/lib/evil/client/dsl/files.rb +37 -0
- data/lib/evil/client/dsl/operation.rb +102 -0
- data/lib/evil/client/dsl/operations.rb +41 -0
- data/lib/evil/client/dsl/scope.rb +34 -0
- data/lib/evil/client/dsl/security.rb +57 -0
- data/lib/evil/client/middleware.rb +81 -0
- data/lib/evil/client/middleware/base.rb +15 -0
- data/lib/evil/client/middleware/merge_security.rb +16 -0
- data/lib/evil/client/middleware/normalize_headers.rb +13 -0
- data/lib/evil/client/middleware/stringify_form.rb +36 -0
- data/lib/evil/client/middleware/stringify_json.rb +15 -0
- data/lib/evil/client/middleware/stringify_multipart.rb +32 -0
- data/lib/evil/client/middleware/stringify_multipart/part.rb +36 -0
- data/lib/evil/client/middleware/stringify_query.rb +31 -0
- data/lib/evil/client/model.rb +65 -0
- data/lib/evil/client/operation.rb +34 -0
- data/lib/evil/client/operation/request.rb +42 -0
- data/lib/evil/client/operation/response.rb +40 -0
- data/lib/evil/client/operation/response_error.rb +12 -0
- data/lib/evil/client/operation/unexpected_response_error.rb +16 -0
- data/mkdocs.yml +21 -0
- data/spec/features/instantiation_spec.rb +68 -0
- data/spec/features/middleware_spec.rb +75 -0
- data/spec/features/operation_with_documentation_spec.rb +41 -0
- data/spec/features/operation_with_files_spec.rb +40 -0
- data/spec/features/operation_with_form_body_spec.rb +158 -0
- data/spec/features/operation_with_headers_spec.rb +99 -0
- data/spec/features/operation_with_http_method_spec.rb +45 -0
- data/spec/features/operation_with_json_body_spec.rb +156 -0
- data/spec/features/operation_with_path_spec.rb +47 -0
- data/spec/features/operation_with_query_spec.rb +84 -0
- data/spec/features/operation_with_response_spec.rb +109 -0
- data/spec/features/operation_with_security_spec.rb +228 -0
- data/spec/features/scoping_spec.rb +48 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/test_client.rb +15 -0
- data/spec/unit/evil/client/connection/net_http_spec.rb +38 -0
- data/spec/unit/evil/client/dsl/files_spec.rb +37 -0
- data/spec/unit/evil/client/dsl/operation_spec.rb +233 -0
- data/spec/unit/evil/client/dsl/operations_spec.rb +27 -0
- data/spec/unit/evil/client/dsl/scope_spec.rb +30 -0
- data/spec/unit/evil/client/dsl/security_spec.rb +135 -0
- data/spec/unit/evil/client/dsl_spec.rb +57 -0
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +32 -0
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +17 -0
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +63 -0
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +61 -0
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +59 -0
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +62 -0
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +40 -0
- data/spec/unit/evil/client/middleware_spec.rb +46 -0
- data/spec/unit/evil/client/model_spec.rb +100 -0
- data/spec/unit/evil/client/operation/request_spec.rb +49 -0
- data/spec/unit/evil/client/operation/response_spec.rb +61 -0
- metadata +271 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
RSpec.describe Evil::Client::DSL::Security do
|
2
|
+
subject { described_class.new(&block).call }
|
3
|
+
|
4
|
+
context "from block without definitions:" do
|
5
|
+
let(:block) { proc {} }
|
6
|
+
|
7
|
+
it "provides an empty schema" do
|
8
|
+
expect(subject).to eq({})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "from block with #key_auth using headers:" do
|
13
|
+
let(:block) do
|
14
|
+
proc do
|
15
|
+
key_auth "foo", "bar"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "provides a schema" do
|
20
|
+
expect(subject).to eq headers: { "foo" => "bar" }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "from block with #key_auth using body:" do
|
25
|
+
let(:block) do
|
26
|
+
proc do
|
27
|
+
key_auth "foo", "bar", using: :body
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "provides a schema" do
|
32
|
+
expect(subject).to eq body: { "foo" => "bar" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "from block with #key_auth using query:" do
|
37
|
+
let(:block) do
|
38
|
+
proc do
|
39
|
+
key_auth "foo", "bar", using: :query
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "provides a schema" do
|
44
|
+
expect(subject).to eq query: { "foo" => "bar" }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "from block with #key_auth using unknown part:" do
|
49
|
+
let(:block) do
|
50
|
+
proc do
|
51
|
+
key_auth "foo", "bar", using: :unknown
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "fails" do
|
56
|
+
expect { subject }.to raise_error ArgumentError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "from block with #token_auth using headers:" do
|
61
|
+
let(:block) do
|
62
|
+
proc do
|
63
|
+
token_auth "foo"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "provides a schema" do
|
68
|
+
expect(subject).to eq headers: { "authorization" => "foo" }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "from block with #token_auth with a prefix:" do
|
73
|
+
let(:block) do
|
74
|
+
proc do
|
75
|
+
token_auth "foo", prefix: "Digest"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "provides a schema" do
|
80
|
+
expect(subject).to eq headers: { "authorization" => "Digest foo" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "from block with #token_auth using body:" do
|
85
|
+
let(:block) do
|
86
|
+
proc do
|
87
|
+
token_auth "foo", using: :body
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "provides a schema" do
|
92
|
+
expect(subject).to eq body: { "access_token" => "foo" }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "from block with #token_auth using query:" do
|
97
|
+
let(:block) do
|
98
|
+
proc do
|
99
|
+
token_auth "foo", using: :query
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it "provides a schema" do
|
104
|
+
expect(subject).to eq query: { "access_token" => "foo" }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "from block with #basic_auth:" do
|
109
|
+
let(:block) do
|
110
|
+
proc do
|
111
|
+
basic_auth "foo", "bar"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it "provides a schema" do
|
116
|
+
expect(subject)
|
117
|
+
.to eq headers: { "authorization" => "Basic Zm9vOmJhcg==" }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "from block with several definitions:" do
|
122
|
+
let(:block) do
|
123
|
+
proc do
|
124
|
+
basic_auth "foo", "bar"
|
125
|
+
token_auth "baz", using: :query
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "provides a full schema" do
|
130
|
+
expect(subject).to eq \
|
131
|
+
headers: { "authorization" => "Basic Zm9vOmJhcg==" },
|
132
|
+
query: { "access_token" => "baz" }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe Evil::Client::DSL do
|
2
|
+
before do
|
3
|
+
class Test::Middleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
app.call(env).reverse
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Test::Foo
|
14
|
+
extend Evil::Client::DSL
|
15
|
+
|
16
|
+
settings do
|
17
|
+
param :version, type: Dry::Types["coercible.int"]
|
18
|
+
option :user, type: Dry::Types["strict.string"]
|
19
|
+
option :password, type: Dry::Types["strict.string"]
|
20
|
+
end
|
21
|
+
|
22
|
+
base_url do |settings|
|
23
|
+
"https://example.com/v#{settings.version}"
|
24
|
+
end
|
25
|
+
|
26
|
+
connection do |_|
|
27
|
+
run Test::Middleware
|
28
|
+
end
|
29
|
+
|
30
|
+
operation do |settings|
|
31
|
+
security { basic_auth settings.user, settings.password }
|
32
|
+
end
|
33
|
+
|
34
|
+
operation :find_user do |_|
|
35
|
+
path { |id:| "users/#{id}" }
|
36
|
+
http_method :get
|
37
|
+
|
38
|
+
response 200 do |data|
|
39
|
+
data.reverse
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
subject { Test::Foo.finalize(4, user: "foo", password: "bar") }
|
46
|
+
|
47
|
+
it "builds a proper schema" do
|
48
|
+
operation = subject[:operations][:find_user]
|
49
|
+
|
50
|
+
expect(operation[:security].call)
|
51
|
+
.to eq headers: { "authorization" => "Basic Zm9vOmJhcg==" }
|
52
|
+
expect(operation[:path].call(id: 3)).to eq "users/3"
|
53
|
+
expect(operation[:method]).to eq "get"
|
54
|
+
|
55
|
+
expect(operation[:responses][200][:coercer].call "foo").to eq "oof"
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::MergeSecurity do
|
2
|
+
let(:stack) { described_class.new(app) }
|
3
|
+
let(:app) { double :app }
|
4
|
+
|
5
|
+
def update!(env)
|
6
|
+
@result = env
|
7
|
+
end
|
8
|
+
|
9
|
+
before { allow(app).to receive(:call) { |env| update! env } }
|
10
|
+
subject { stack.call env }
|
11
|
+
|
12
|
+
let(:env) do
|
13
|
+
{
|
14
|
+
body: { "foo" => :FOO, "access_key" => :FOO },
|
15
|
+
query: { "bar" => :BAR, "access_key" => :FOO },
|
16
|
+
headers: { "baz" => :BAZ, "authorization" => :FOO },
|
17
|
+
security: {
|
18
|
+
body: { "access_key" => :QUX },
|
19
|
+
query: { "access_key" => :ZYX },
|
20
|
+
headers: { "authorization" => "Basic 3ou08314tq==" }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it "merges security schema to env" do
|
26
|
+
subject
|
27
|
+
expect(@result).to eq \
|
28
|
+
body: { "foo" => :FOO, "access_key" => :QUX },
|
29
|
+
query: { "bar" => :BAR, "access_key" => :ZYX },
|
30
|
+
headers: { "baz" => :BAZ, "authorization" => "Basic 3ou08314tq==" }
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::NormalizeHeaders do
|
2
|
+
let(:stack) { described_class.new(app) }
|
3
|
+
let(:app) { double :app }
|
4
|
+
let(:env) { { headers: { Foo: :BAR } } }
|
5
|
+
|
6
|
+
def update!(env)
|
7
|
+
@result = env
|
8
|
+
end
|
9
|
+
|
10
|
+
before { allow(app).to receive(:call) { |env| update! env } }
|
11
|
+
subject { stack.call env }
|
12
|
+
|
13
|
+
it "normalizes headers" do
|
14
|
+
subject
|
15
|
+
expect(@result[:headers]).to eq "foo" => "BAR"
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::StringifyForm do
|
2
|
+
let(:stack) { described_class.new(app) }
|
3
|
+
let(:app) { double :app }
|
4
|
+
|
5
|
+
def update!(env)
|
6
|
+
@result = env
|
7
|
+
end
|
8
|
+
|
9
|
+
before { allow(app).to receive(:call) { |env| update! env } }
|
10
|
+
subject { stack.call env }
|
11
|
+
|
12
|
+
context "with a non-empty body:" do
|
13
|
+
let(:env) do
|
14
|
+
{
|
15
|
+
body: { foo: :FOO, bar: [:BAR], baz: { qux: :QUX }, qux: nil },
|
16
|
+
format: "form"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
it "stringifies the body" do
|
21
|
+
subject
|
22
|
+
expect(@result[:body_string]).to eq "foo=FOO&bar[]=BAR&baz[qux]=QUX&qux="
|
23
|
+
end
|
24
|
+
|
25
|
+
it "adds content-type header" do
|
26
|
+
subject
|
27
|
+
expect(@result[:headers])
|
28
|
+
.to eq "content-type" => "application/x-www-form-urlencoded"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when format is not a form:" do
|
33
|
+
let(:env) do
|
34
|
+
{
|
35
|
+
body: { foo: :FOO, bar: [:BAR], baz: { qux: :QUX }, qux: nil },
|
36
|
+
format: "json"
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
it "does nothing" do
|
41
|
+
subject
|
42
|
+
expect(@result).to eq env
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with empty body:" do
|
47
|
+
let(:env) { { body: {}, format: "form" } }
|
48
|
+
|
49
|
+
it "does nothing" do
|
50
|
+
subject
|
51
|
+
expect(@result).to eq env
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "without a body:" do
|
56
|
+
let(:env) { { format: "form" } }
|
57
|
+
|
58
|
+
it "does nothing" do
|
59
|
+
subject
|
60
|
+
expect(@result).to eq env
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::StringifyJson do
|
2
|
+
def update!(result)
|
3
|
+
@result = result
|
4
|
+
end
|
5
|
+
|
6
|
+
let(:app) { double :app }
|
7
|
+
|
8
|
+
before { allow(app).to receive(:call) { |env| update!(env) } }
|
9
|
+
subject { described_class.new(app).call(env) }
|
10
|
+
|
11
|
+
context "with a body:" do
|
12
|
+
let(:env) { { format: "json", body: { foo: :bar } } }
|
13
|
+
|
14
|
+
it "stringifies a body" do
|
15
|
+
subject
|
16
|
+
expect(@result[:body_string]).to eq '{"foo":"bar"}'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets content-type" do
|
20
|
+
subject
|
21
|
+
expect(@result[:headers]).to eq "content-type" => "application/json"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when format is not json:" do
|
26
|
+
let(:env) { { format: "form", body: { foo: :bar } } }
|
27
|
+
|
28
|
+
it "does nothing" do
|
29
|
+
subject
|
30
|
+
expect(@result).to eq env
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with empty body:" do
|
35
|
+
let(:env) { { format: "json", body: {} } }
|
36
|
+
|
37
|
+
it "stringifies an empty body" do
|
38
|
+
subject
|
39
|
+
expect(@result[:body_string]).to eq "{}"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets content-type" do
|
43
|
+
subject
|
44
|
+
expect(@result[:headers]).to eq "content-type" => "application/json"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "without a body:" do
|
49
|
+
let(:env) { { format: "json" } }
|
50
|
+
|
51
|
+
it "stringifies an empty body" do
|
52
|
+
subject
|
53
|
+
expect(@result[:body_string]).to eq "{}"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "sets content-type" do
|
57
|
+
subject
|
58
|
+
expect(@result[:headers]).to eq "content-type" => "application/json"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::StringifyMultipart::Part do
|
2
|
+
let(:file) { StringIO.new "Hello!" }
|
3
|
+
let(:type) { MIME::Types["text/html"].first }
|
4
|
+
let(:charset) { "utf-8" }
|
5
|
+
let(:part) do
|
6
|
+
described_class.new(file: file, type: type, charset: charset)
|
7
|
+
end
|
8
|
+
|
9
|
+
subject { part.to_s }
|
10
|
+
|
11
|
+
shared_examples :building_part do
|
12
|
+
it "includes content disposition" do
|
13
|
+
expect(subject).to include \
|
14
|
+
'Content-Disposition: form-data; name="AttachedFile"; filename='
|
15
|
+
end
|
16
|
+
|
17
|
+
it "includes content type" do
|
18
|
+
expect(subject).to include "Content-Type: text/html; charset=utf-8"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "includes content" do
|
22
|
+
expect(subject).to include "Hello!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with a name" do
|
27
|
+
let(:part) { described_class.new(file: file, name: "UploadedFile") }
|
28
|
+
|
29
|
+
it "includes part name" do
|
30
|
+
expect(subject).to include 'name="UploadedFile"'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with a filename" do
|
35
|
+
let(:part) { described_class.new(file: file, filename: "weird_thing.json") }
|
36
|
+
|
37
|
+
it "includes part name" do
|
38
|
+
expect(subject).to include 'filename="weird_thing.json"'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "from file" do
|
43
|
+
let(:file) { instance_double ::File, path: "foo/bar.html", read: "Hello!" }
|
44
|
+
it_behaves_like :building_part
|
45
|
+
|
46
|
+
it "includes filename" do
|
47
|
+
expect(subject).to include 'filename="bar.html"'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "from io" do
|
52
|
+
it_behaves_like :building_part
|
53
|
+
end
|
54
|
+
|
55
|
+
context "from text" do
|
56
|
+
let(:file) { "Hello!" }
|
57
|
+
it_behaves_like :building_part
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
RSpec.describe Evil::Client::Middleware::StringifyMultipart do
|
2
|
+
let(:app) { double :app }
|
3
|
+
let(:type) { "file" }
|
4
|
+
let(:env) do
|
5
|
+
{
|
6
|
+
format: "multipart",
|
7
|
+
headers: {},
|
8
|
+
files: [
|
9
|
+
{
|
10
|
+
file: StringIO.new('{"text":"Hello!"}'),
|
11
|
+
type: MIME::Types["application/json"].first,
|
12
|
+
charset: "utf-16",
|
13
|
+
filename: "greetings.json"
|
14
|
+
}
|
15
|
+
]
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def update!(result)
|
20
|
+
@result = result
|
21
|
+
end
|
22
|
+
|
23
|
+
before { allow(app).to receive(:call) { |env| update!(env) } }
|
24
|
+
subject { described_class.new(app).call(env) }
|
25
|
+
|
26
|
+
context "with a multipart format:" do
|
27
|
+
let(:body_string) { @result[:body_string] }
|
28
|
+
|
29
|
+
it "builds multipart body_string" do
|
30
|
+
subject
|
31
|
+
expect(body_string).to include '{"text":"Hello!"}'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "uses name and filename" do
|
35
|
+
subject
|
36
|
+
expect(body_string).to include "Content-Disposition: form-data;" \
|
37
|
+
' name="AttachedFile1";' \
|
38
|
+
' filename="greetings.json"'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "uses content-type and charset" do
|
42
|
+
subject
|
43
|
+
expect(body_string)
|
44
|
+
.to include "Content-Type: application/json; charset=utf-16"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "adds the header" do
|
48
|
+
subject
|
49
|
+
expect(@result[:headers]["content-type"])
|
50
|
+
.to include "multipart/form-data; boundary="
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with non-multipart format:" do
|
55
|
+
before { env[:format] = "json" }
|
56
|
+
|
57
|
+
it "does nothing" do
|
58
|
+
subject
|
59
|
+
expect(@result).to eq env
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|