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,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