evil-client 1.0.0 → 1.1.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 +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +1 -1
- data/docs/index.md +1 -1
- data/docs/rspec.md +60 -30
- data/evil-client.gemspec +2 -2
- data/lib/evil/client.rb +5 -0
- data/lib/evil/client/builder.rb +2 -0
- data/lib/evil/client/chaining.rb +2 -0
- data/lib/evil/client/container.rb +16 -2
- data/lib/evil/client/exceptions/name_error.rb +5 -7
- data/lib/evil/client/names.rb +54 -0
- data/lib/evil/client/options.rb +27 -0
- data/lib/evil/client/resolver.rb +2 -0
- data/lib/evil/client/rspec.rb +18 -120
- data/lib/evil/client/rspec/allow_stub.rb +33 -0
- data/lib/evil/client/rspec/base_stub.rb +26 -0
- data/lib/evil/client/rspec/evil_client_schema_matching.rb +21 -0
- data/lib/evil/client/rspec/expect_stub.rb +15 -0
- data/lib/evil/client/schema.rb +3 -1
- data/lib/evil/client/schema/scope.rb +2 -5
- data/lib/evil/client/settings.rb +5 -9
- data/lib/evil/client/settings/validator.rb +1 -1
- data/spec/unit/container_spec.rb +18 -0
- data/spec/unit/exceptions/name_error_spec.rb +5 -10
- data/spec/unit/options_spec.rb +20 -0
- data/spec/unit/rspec/evil_client_shema_matching_spec.rb +37 -0
- data/spec/unit/rspec/expect_client_operation_spec.rb +143 -0
- data/spec/unit/rspec/stub_client_operation_spec.rb +168 -0
- data/spec/unit/settings_spec.rb +5 -0
- metadata +18 -6
- data/spec/unit/rspec_spec.rb +0 -342
@@ -0,0 +1,33 @@
|
|
1
|
+
# @private
|
2
|
+
module Evil::Client::RSpec
|
3
|
+
#
|
4
|
+
# Container to chain settings for allowing operation(s)
|
5
|
+
#
|
6
|
+
class AllowStub < BaseStub
|
7
|
+
def to_return(response = nil)
|
8
|
+
allow(Evil::Client::Container::Operation)
|
9
|
+
.to follow_expectation
|
10
|
+
.and_return double(:operation, call: response)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_raise(error = StandardError, *args)
|
14
|
+
allow(Evil::Client::Container::Operation)
|
15
|
+
.to follow_expectation
|
16
|
+
.and_return proc { raise(error, *args) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_call_original
|
20
|
+
allow(Evil::Client::Container::Operation)
|
21
|
+
.to follow_expectation
|
22
|
+
.and_call_original
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def follow_expectation
|
28
|
+
receive(:new).with evil_client_schema_matching(@klass, @name),
|
29
|
+
*any_args, # logger
|
30
|
+
@condition || anything
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# @private
|
2
|
+
module Evil::Client::RSpec
|
3
|
+
#
|
4
|
+
# Container to chain settings for stubbing operation(s)
|
5
|
+
#
|
6
|
+
class BaseStub
|
7
|
+
include RSpec::Mocks::ExampleMethods
|
8
|
+
include RSpec::Matchers
|
9
|
+
|
10
|
+
def with(condition = nil, &block)
|
11
|
+
update(condition || block)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def initialize(klass, name = nil, condition: nil)
|
17
|
+
@klass = klass
|
18
|
+
@name = name
|
19
|
+
@condition = condition
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(condition)
|
23
|
+
self.class.new @klass, @name, condition: condition
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# @private
|
2
|
+
module Evil::Client::RSpec
|
3
|
+
#
|
4
|
+
# Checks whether an operation schema matches klass and name
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# expect(schema).to evil_client_schema_matching(MyClient, /users/)
|
8
|
+
#
|
9
|
+
::RSpec::Matchers.define :evil_client_schema_matching do |klass, name = nil|
|
10
|
+
match do |schema|
|
11
|
+
expect(schema).to be_instance_of(Evil::Client::Schema::Operation)
|
12
|
+
expect(schema.client.ancestors).to include(klass)
|
13
|
+
|
14
|
+
case name
|
15
|
+
when NilClass then true
|
16
|
+
when Regexp then schema.to_s[name]
|
17
|
+
else schema.to_s == "#{klass}.#{name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# @private
|
2
|
+
module Evil::Client::RSpec
|
3
|
+
#
|
4
|
+
# Container to chain settings for allowing operation(s)
|
5
|
+
#
|
6
|
+
class ExpectStub < BaseStub
|
7
|
+
def to_have_been_performed
|
8
|
+
expect(Evil::Client::Container::Operation)
|
9
|
+
.to have_received(:new)
|
10
|
+
.with evil_client_schema_matching(@klass, @name),
|
11
|
+
*any_args, # logger
|
12
|
+
@condition || anything
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/evil/client/schema.rb
CHANGED
@@ -9,6 +9,8 @@ class Evil::Client
|
|
9
9
|
# definitions.
|
10
10
|
#
|
11
11
|
class Schema
|
12
|
+
Names.clean(self) # Remove unnecessary methods from the instance
|
13
|
+
|
12
14
|
# Loads concrete implementations of the abstract schema
|
13
15
|
require_relative "schema/operation"
|
14
16
|
require_relative "schema/scope"
|
@@ -33,7 +35,7 @@ class Evil::Client
|
|
33
35
|
#
|
34
36
|
# @return [Evil::Client]
|
35
37
|
#
|
36
|
-
attr_reader :client
|
38
|
+
attr_reader :client
|
37
39
|
|
38
40
|
# The human-friendly representation of the schema
|
39
41
|
#
|
@@ -39,7 +39,7 @@ class Evil::Client
|
|
39
39
|
# @return [self]
|
40
40
|
#
|
41
41
|
def scope(name, &block)
|
42
|
-
key = NameError.check!(name
|
42
|
+
key = NameError.check!(name)
|
43
43
|
TypeError.check! self, key, :scope
|
44
44
|
@__children__[key] ||= self.class.new(self, key)
|
45
45
|
@__children__[key].instance_exec(&block)
|
@@ -53,7 +53,7 @@ class Evil::Client
|
|
53
53
|
# @return [self]
|
54
54
|
#
|
55
55
|
def operation(name, &block)
|
56
|
-
key = NameError.check!(name
|
56
|
+
key = NameError.check!(name)
|
57
57
|
TypeError.check! self, key, :operation
|
58
58
|
@__children__[key] ||= self.class.superclass.new(self, key)
|
59
59
|
@__children__[key].instance_exec(&block)
|
@@ -66,8 +66,5 @@ class Evil::Client
|
|
66
66
|
super
|
67
67
|
@__children__ = {}
|
68
68
|
end
|
69
|
-
|
70
|
-
RESERVED = \
|
71
|
-
%i[operations scopes scope options schema settings inspect logger].freeze
|
72
69
|
end
|
73
70
|
end
|
data/lib/evil/client/settings.rb
CHANGED
@@ -3,9 +3,9 @@ class Evil::Client
|
|
3
3
|
# Container for settings assigned to some operation or scope.
|
4
4
|
#
|
5
5
|
class Settings
|
6
|
+
Names.clean(self) # Remove unnecessary methods from the instance
|
6
7
|
require_relative "settings/validator"
|
7
|
-
|
8
|
-
extend Dry::Initializer
|
8
|
+
extend ::Dry::Initializer
|
9
9
|
|
10
10
|
class << self
|
11
11
|
# The schema klass settings belongs to
|
@@ -34,7 +34,7 @@ class Evil::Client
|
|
34
34
|
# @return [self]
|
35
35
|
#
|
36
36
|
def option(key, type = nil, as: key.to_sym, **opts)
|
37
|
-
NameError.check!(as
|
37
|
+
NameError.check!(as)
|
38
38
|
super
|
39
39
|
self
|
40
40
|
end
|
@@ -46,7 +46,7 @@ class Evil::Client
|
|
46
46
|
# @return [self]
|
47
47
|
#
|
48
48
|
def let(key, &block)
|
49
|
-
NameError.check!(key
|
49
|
+
NameError.check!(key)
|
50
50
|
define_method(key) do
|
51
51
|
instance_variable_get(:"@#{key}") ||
|
52
52
|
instance_variable_set(:"@#{key}", instance_exec(&block))
|
@@ -97,10 +97,6 @@ class Evil::Client
|
|
97
97
|
rescue => error
|
98
98
|
raise ValidationError, error.message
|
99
99
|
end
|
100
|
-
|
101
|
-
# @private
|
102
|
-
RESERVED = \
|
103
|
-
%i[options datetime scope logger basic_auth key_auth token_auth].freeze
|
104
100
|
end
|
105
101
|
|
106
102
|
# The processed hash of options contained by the instance of settings
|
@@ -108,7 +104,7 @@ class Evil::Client
|
|
108
104
|
# @return [Hash<Symbol, Object>]
|
109
105
|
#
|
110
106
|
def options
|
111
|
-
@__options__
|
107
|
+
Options.new @__options__
|
112
108
|
end
|
113
109
|
|
114
110
|
# @!attribute logger
|
data/spec/unit/container_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
RSpec.describe Evil::Client::Container do
|
2
|
+
let(:klass) { double :class }
|
2
3
|
let(:container) { described_class.new schema, logger, opts }
|
3
4
|
let(:logger) { Logger.new log }
|
4
5
|
let(:log) { StringIO.new }
|
@@ -18,6 +19,7 @@ RSpec.describe Evil::Client::Container do
|
|
18
19
|
let(:schema) do
|
19
20
|
double :schema,
|
20
21
|
to_s: "MyApi.users",
|
22
|
+
client: klass,
|
21
23
|
parent: nil,
|
22
24
|
settings: settings_klass,
|
23
25
|
definitions: {}
|
@@ -37,6 +39,14 @@ RSpec.describe Evil::Client::Container do
|
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
42
|
+
describe "#name" do
|
43
|
+
subject { container.name }
|
44
|
+
|
45
|
+
it "returns human-friendly representation of the schema" do
|
46
|
+
expect(subject).to eq "MyApi.users"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
40
50
|
describe "#to_s" do
|
41
51
|
subject { container.to_s }
|
42
52
|
|
@@ -97,6 +107,14 @@ RSpec.describe Evil::Client::Container do
|
|
97
107
|
end
|
98
108
|
end
|
99
109
|
|
110
|
+
describe "#client" do
|
111
|
+
subject { container.client }
|
112
|
+
|
113
|
+
it "returns current client" do
|
114
|
+
expect(subject).to eq klass
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
100
118
|
describe "#options" do
|
101
119
|
subject { container.options }
|
102
120
|
|
@@ -1,10 +1,9 @@
|
|
1
1
|
RSpec.describe Evil::Client::NameError do
|
2
|
-
let(:error)
|
3
|
-
let(:name)
|
4
|
-
let(:forbidden) { %i[foo bar] }
|
2
|
+
let(:error) { described_class.new name }
|
3
|
+
let(:name) { "object_id" }
|
5
4
|
|
6
5
|
describe ".check!" do
|
7
|
-
subject { described_class.check! name
|
6
|
+
subject { described_class.check! name }
|
8
7
|
|
9
8
|
context "with a valid name" do
|
10
9
|
let(:name) { "qux_3" }
|
@@ -55,7 +54,7 @@ RSpec.describe Evil::Client::NameError do
|
|
55
54
|
end
|
56
55
|
|
57
56
|
context "with a forbidden name" do
|
58
|
-
let(:name) { "
|
57
|
+
let(:name) { "object_id" }
|
59
58
|
|
60
59
|
it "raises itself" do
|
61
60
|
expect { subject }.to raise_error described_class
|
@@ -67,11 +66,7 @@ RSpec.describe Evil::Client::NameError do
|
|
67
66
|
subject { error.message }
|
68
67
|
|
69
68
|
it "builds a proper error message" do
|
70
|
-
expect(subject).to
|
71
|
-
" It should contain latin letters in the lower case," \
|
72
|
-
" digits, and underscores only; have minimum 2 chars;" \
|
73
|
-
" start from a letter; end with either letter or digit." \
|
74
|
-
" The following names: 'foo', 'bar' are already used by Evil::Client."
|
69
|
+
expect(subject).to include "Invalid name :object_id."
|
75
70
|
end
|
76
71
|
end
|
77
72
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
RSpec.describe Evil::Client::Options do
|
2
|
+
let(:options) { described_class.new source }
|
3
|
+
let(:source) { { foo: :FOO, bar: :BAR, baz: :BAZ } }
|
4
|
+
|
5
|
+
describe "#slice" do
|
6
|
+
subject { source.slice :foo, :baz }
|
7
|
+
|
8
|
+
it "slices keys from a hash" do
|
9
|
+
expect(subject).to eq foo: :FOO, baz: :BAZ
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#except" do
|
14
|
+
subject { source.except :foo, :baz }
|
15
|
+
|
16
|
+
it "removes keys from a hash" do
|
17
|
+
expect(subject).to eq bar: :BAR
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
RSpec.describe "rspec matcher :evil_client_schema_matching" do
|
2
|
+
before { load "spec/fixtures/test_client.rb" }
|
3
|
+
|
4
|
+
let(:client) { Test::Client.new(subdomain: "foo", user: "bar", token: "baz") }
|
5
|
+
let(:users) { client.crm(version: 7).users }
|
6
|
+
let(:schema) { users.operations[:fetch].schema }
|
7
|
+
|
8
|
+
it "passes when schema matches a client" do
|
9
|
+
expect(schema).to evil_client_schema_matching(Test::Client)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "fails when schema not matches a client superclass" do
|
13
|
+
expect(schema).to evil_client_schema_matching(Evil::Client)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "fails when schema not matches a client" do
|
17
|
+
expect(schema).not_to evil_client_schema_matching(String)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "passes when schema matches client and full path" do
|
21
|
+
expect(schema)
|
22
|
+
.to evil_client_schema_matching(Test::Client, "crm.users.fetch")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "failse when schema matches client but not a full path" do
|
26
|
+
expect(schema).not_to evil_client_schema_matching(Test::Client, "crm.users")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "passes when schema matches client and path regex" do
|
30
|
+
expect(schema).to evil_client_schema_matching(Test::Client, /crm\.users/)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "passes when schema matches client and path regex" do
|
34
|
+
expect(schema)
|
35
|
+
.not_to evil_client_schema_matching(Test::Client, /crm\.fetch/)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
RSpec.describe Evil::Client::RSpec, "#expect_client_operation" do
|
2
|
+
include described_class
|
3
|
+
before { load "spec/fixtures/test_client.rb" }
|
4
|
+
before { stub_client_operation.to_return }
|
5
|
+
|
6
|
+
let(:client) { Test::Client.new(subdomain: "x", user: "y", token: "z") }
|
7
|
+
let(:perform) { client.crm(version: 7).users.fetch id: 5 }
|
8
|
+
|
9
|
+
before { perform }
|
10
|
+
|
11
|
+
context "with client class" do
|
12
|
+
let(:klass) { Test::Client }
|
13
|
+
|
14
|
+
it "passes" do
|
15
|
+
expect { expect_client_operation(klass).to_have_been_performed }
|
16
|
+
.not_to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with client's superclass" do
|
21
|
+
let(:klass) { Evil::Client }
|
22
|
+
|
23
|
+
it "passes" do
|
24
|
+
expect { expect_client_operation(klass).to_have_been_performed }
|
25
|
+
.not_to raise_error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "with neither a client nor its superclass" do
|
30
|
+
let(:klass) { String }
|
31
|
+
|
32
|
+
it "fails" do
|
33
|
+
expect { expect_client_operation(klass).to_have_been_performed }
|
34
|
+
.to raise_error RSpec::Expectations::ExpectationNotMetError
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with client class and fully qualified name" do
|
39
|
+
let(:klass) { Test::Client }
|
40
|
+
let(:name) { "crm.users.fetch" }
|
41
|
+
|
42
|
+
it "passes" do
|
43
|
+
expect do
|
44
|
+
expect_client_operation(klass, name).to_have_been_performed
|
45
|
+
end.not_to raise_error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with client class and underqualified name" do
|
50
|
+
let(:klass) { Test::Client }
|
51
|
+
let(:name) { "crm.users" }
|
52
|
+
|
53
|
+
it "fails" do
|
54
|
+
expect do
|
55
|
+
expect_client_operation(klass, name).to_have_been_performed
|
56
|
+
end.to raise_error RSpec::Expectations::ExpectationNotMetError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with client class and partially qualified name" do
|
61
|
+
let(:klass) { Test::Client }
|
62
|
+
let(:name) { /crm\.users/ }
|
63
|
+
|
64
|
+
it "passes" do
|
65
|
+
expect do
|
66
|
+
expect_client_operation(klass, name).to_have_been_performed
|
67
|
+
end.not_to raise_error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with client class and wrongly qualified name" do
|
72
|
+
let(:klass) { Test::Client }
|
73
|
+
let(:name) { /^crm.users$/ }
|
74
|
+
|
75
|
+
it "fails" do
|
76
|
+
expect do
|
77
|
+
expect_client_operation(klass, name).to_have_been_performed
|
78
|
+
end.to raise_error RSpec::Expectations::ExpectationNotMetError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "with client class and expected options" do
|
83
|
+
let(:klass) { Test::Client }
|
84
|
+
let(:name) { "crm.users.fetch" }
|
85
|
+
let(:opts) do
|
86
|
+
{ subdomain: "x", user: "y", token: "z", version: 7, id: 5 }
|
87
|
+
end
|
88
|
+
|
89
|
+
it "passes" do
|
90
|
+
expect do
|
91
|
+
expect_client_operation(klass, name)
|
92
|
+
.with(opts)
|
93
|
+
.to_have_been_performed
|
94
|
+
end.not_to raise_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "with client class and block expectation" do
|
99
|
+
let(:klass) { Test::Client }
|
100
|
+
let(:name) { "crm.users.fetch" }
|
101
|
+
let(:opts) do
|
102
|
+
{ subdomain: "x", user: "y", token: "z", version: 7, id: 5 }
|
103
|
+
end
|
104
|
+
|
105
|
+
it "passes" do
|
106
|
+
expect do
|
107
|
+
expect_client_operation(klass, name)
|
108
|
+
.with { |o| o == opts }
|
109
|
+
.to_have_been_performed
|
110
|
+
end.not_to raise_error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with client class and overexpected options" do
|
115
|
+
let(:klass) { Test::Client }
|
116
|
+
let(:name) { "crm.users.fetch" }
|
117
|
+
let(:opts) do
|
118
|
+
{ subdomain: "x", user: "y", token: "z", version: 7 }
|
119
|
+
end
|
120
|
+
|
121
|
+
it "fails" do
|
122
|
+
expect do
|
123
|
+
expect_client_operation(klass, name)
|
124
|
+
.with { |o| o == opts }
|
125
|
+
.to_have_been_performed
|
126
|
+
end.to raise_error RSpec::Expectations::ExpectationNotMetError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "with client class and partially expected options" do
|
131
|
+
let(:klass) { Test::Client }
|
132
|
+
let(:name) { "crm.users.fetch" }
|
133
|
+
let(:opts) { hash_including subdomain: "x" }
|
134
|
+
|
135
|
+
it "passes" do
|
136
|
+
expect do
|
137
|
+
expect_client_operation(klass, name)
|
138
|
+
.with(opts)
|
139
|
+
.to_have_been_performed
|
140
|
+
end.not_to raise_error
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|