evil-client 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 # TODO: add spec
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, RESERVED)
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, RESERVED)
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
@@ -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, RESERVED)
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, RESERVED)
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
@@ -55,7 +55,7 @@ class Evil::Client
55
55
  end
56
56
 
57
57
  def validate!(settings, block)
58
- settings.instance_eval(&block) && true
58
+ settings.instance_exec(&block) && true
59
59
  rescue => error
60
60
  settings&.logger&.error(self) { "broken for #{settings} with #{error}" }
61
61
  raise
@@ -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) { described_class.new name, forbidden }
3
- let(:name) { "foo" }
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, forbidden }
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) { "foo" }
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 eq "Invalid name :foo." \
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