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.
@@ -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