salestation 4.0.2 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85ac2feb504421f1ab20912abbf6994765d3ad6b743360a4e47c67a5bb362774
4
- data.tar.gz: 3e6571aeb33d1c2edd94857a6de854e477f8e3a87a2abeac8d352634e01e3012
3
+ metadata.gz: ee690e689bb403535e57cdab44d34b9c909e40c45373ce2286d2d0d5548b2c1b
4
+ data.tar.gz: e2bef8563d3f2a1acd4d0a234da31989a9e4e26c271ebeb363a2c9398f581090
5
5
  SHA512:
6
- metadata.gz: 6d0a4278a94c18bc1a5a5286c0b3941701fda3182d9bcbc27d5b29ccf57841b296b618a75ef67fe1e8a3e88f8c5a649fc3100803d2e79c1d812b0f90b2c13e12
7
- data.tar.gz: 80a64adaf82c53f673926a47cc389ae8554c589aa52e41c84ee23a68807d1c49b098c72bf5a338cd0255f689176c0e44f15b7fe0389ea3a90ec89d0bd3a54827
6
+ metadata.gz: 5542762ed338dad4d06b993837cdac127fc813213ca8f0309183817893f3a0e84ee3dff126313cd5047c3d6a31253400c2e8cf854893ffe56bc820b16ce9339e
7
+ data.tar.gz: a3077f9493f67d0aaf5c5ad9769a164af0f379bbfce6690e53ac9d12a8678c58992759651f5acb808e9cd12155dd50de59673fb2f1b927abead0068fbb53d541
data/README.md CHANGED
@@ -184,6 +184,29 @@ You can configure per-request tags by defining `salestation.statsd.tags` in sina
184
184
  end
185
185
  ```
186
186
 
187
+ ## RSpec helpers
188
+
189
+
190
+ To use Salestation RSpec matchers you first need to include them:
191
+
192
+ ```
193
+ require 'salestation/rspec'
194
+
195
+ RSpec.configure do |config|
196
+ config.include Salestation::RSpec::Matchers
197
+ end
198
+ ```
199
+
200
+ After that it's possible to match against error results like this:
201
+
202
+ ```
203
+ expect(result).to be_failure
204
+ .with_conflict
205
+ .containing(message: 'Oh noes')
206
+ ```
207
+
208
+ See lib/salestation/rspec.rb for more examples.
209
+
187
210
  ## Development
188
211
 
189
212
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -7,7 +7,12 @@ require 'dry-types'
7
7
  module Salestation
8
8
  class App
9
9
  module Types
10
- include Dry::Types()
10
+ dry_types_version = Gem.loaded_specs['dry-types'].version
11
+ if dry_types_version < Gem::Version.new('0.15.0')
12
+ include Dry::Types.module
13
+ else
14
+ include Dry::Types()
15
+ end
11
16
  end
12
17
 
13
18
  def initialize(env:, hooks: {})
@@ -12,44 +12,44 @@ module Salestation
12
12
  end
13
13
 
14
14
  class InvalidInput < Error
15
- attribute :errors, Types::Strict::Hash
16
- attribute :hints, Types::Coercible::Hash.default({}.freeze)
15
+ attribute? :errors, Types::Strict::Hash
16
+ attribute? :hints, Types::Coercible::Hash.default({}.freeze)
17
17
  attribute? :debug_message, Types::Strict::String
18
18
  attribute? :form_errors, Types::Strict::Bool.default(false)
19
19
  end
20
20
 
21
21
  class DependencyCurrentlyUnavailable < Error
22
- attribute :message, Types::Strict::String
22
+ attribute? :message, Types::Strict::String
23
23
  attribute? :debug_message, Types::Strict::String
24
24
  end
25
25
 
26
26
  class RequestedResourceNotFound < Error
27
- attribute :message, Types::Strict::String
27
+ attribute? :message, Types::Strict::String
28
28
  attribute? :debug_message, Types::Strict::String
29
29
  end
30
30
 
31
31
  class Forbidden < Error
32
- attribute :message, Types::Strict::String
32
+ attribute? :message, Types::Strict::String
33
33
  attribute? :debug_message, Types::Strict::String
34
34
  end
35
35
 
36
36
  class Conflict < Error
37
- attribute :message, Types::Strict::String
38
- attribute :debug_message, Types::Strict::String
37
+ attribute? :message, Types::Strict::String
38
+ attribute? :debug_message, Types::Strict::String
39
39
  end
40
40
 
41
41
  class NotAcceptable < Error
42
- attribute :message, Types::Strict::String
43
- attribute :debug_message, Types::Strict::String
42
+ attribute? :message, Types::Strict::String
43
+ attribute? :debug_message, Types::Strict::String
44
44
  end
45
45
 
46
46
  class UnsupportedMediaType < Error
47
- attribute :message, Types::Strict::String
48
- attribute :debug_message, Types::Strict::String
47
+ attribute? :message, Types::Strict::String
48
+ attribute? :debug_message, Types::Strict::String
49
49
  end
50
50
 
51
51
  class RequestEntityTooLarge < Error
52
- attribute :message, Types::Strict::String
52
+ attribute? :message, Types::Strict::String
53
53
  attribute? :debug_message, Types::Strict::String
54
54
  end
55
55
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+ require 'salestation/app'
5
+
6
+ require_relative './rspec/failure_matcher'
7
+ require_relative './rspec/glia_input_validation_error_matcher'
8
+
9
+ module Salestation
10
+ module RSpec
11
+ # Usage:
12
+ #
13
+ # In your RSpec configuration first include the matchers:
14
+ #
15
+ # RSpec.configure do |config|
16
+ # config.include Salestation::RSpec::Matchers
17
+ # end
18
+ #
19
+ # Then you can use the matchers like this:
20
+ #
21
+ # expect(result).to be_failure
22
+ # .with_conflict
23
+ # .containing(message: 'Oh noes')
24
+ #
25
+ # or when using Glia Errors:
26
+ #
27
+ # expect(result).to be_failure
28
+ # .with_requested_resource_not_found
29
+ # .containing(Glia::Errors::ResourceNotFoundError.new(resource: :user))
30
+ #
31
+ # or when matching input errors from Glia Errors:
32
+ #
33
+ # expect(result).to be_failure
34
+ # .with_invalid_input
35
+ # .containing(glia_input_validation_error.on(:name).with_type(Glia::Errors::INVALID_VALUE_ERROR))
36
+ #
37
+ # or you could even match multiple input errors at the same time:
38
+ # expect(result).to be_failure
39
+ # .with_invalid_input
40
+ # .containing(
41
+ # glia_input_validation_error
42
+ # .on(:name).with_type(Glia::Errors::INVALID_VALUE_ERROR)
43
+ # .on(:email)
44
+ # .on(:phone_number).with_type(Glia::Errors::INVALID_FORMAT_ERROR)
45
+ # )
46
+ #
47
+ # Everything after be_failure is optional. You could also use `.containing`
48
+ # multiple times like this:
49
+ #
50
+ # expect(result).to be_failure
51
+ # .containing(Glia::Errors::ResourceNotFoundError.new(resource: :user))
52
+ # .containing(hash_including(message: 'Overriden message'))
53
+ #
54
+ module Matchers
55
+ def be_failure
56
+ FailureMatcher.new
57
+ end
58
+
59
+ def glia_input_validation_error
60
+ GliaInputValidationErrorMatcher.new
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ module Salestation
6
+ module RSpec
7
+ class FailureMatcher
8
+ include ::RSpec::Matchers::Composable
9
+
10
+ attr_reader :expected, :actual, :failure_message
11
+
12
+ def initialize
13
+ @contents_matchers = []
14
+ end
15
+
16
+ def with(error_class)
17
+ @error_class = error_class
18
+ self
19
+ end
20
+
21
+ # From: active_support/inflector/methods.rb
22
+ def self.underscore(camel_cased_word)
23
+ return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
24
+ word = camel_cased_word.to_s.gsub("::", "/")
25
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
26
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
27
+ word.tr!("-", "_")
28
+ word.downcase!
29
+ word
30
+ end
31
+ private_class_method :underscore
32
+
33
+ # Defined methods using error class names:
34
+ # with_conflict
35
+ # with_invalid_input
36
+ # ...
37
+ Salestation::App::Errors.constants.each do |class_name|
38
+ define_method(:"with_#{underscore(class_name.to_s)}") do
39
+ with(Salestation::App::Errors.const_get(class_name))
40
+ self
41
+ end
42
+ end
43
+
44
+ def containing(matcher)
45
+ @contents_matchers << matcher
46
+ self
47
+ end
48
+
49
+ def matches?(actual)
50
+ check_failure(actual) &&
51
+ check_error_type(actual) &&
52
+ check_contents(actual)
53
+ end
54
+
55
+ def diffable?
56
+ true
57
+ end
58
+
59
+ private
60
+
61
+ def check_failure(actual)
62
+ return true if actual.class == Deterministic::Result::Failure
63
+
64
+ @failure_message = "Expected Failure(...), but got #{actual.inspect}"
65
+ @expected = Deterministic::Result::Failure
66
+ @actual = actual.class
67
+ false
68
+ end
69
+
70
+ def check_error_type(actual)
71
+ return true unless @error_class
72
+ return true if actual.value.class == @error_class
73
+
74
+ @failure_message = "Expected failure to include #{@error_class}, but got #{actual.value.inspect}"
75
+ @expected = @error_class
76
+ @actual = actual.value.class
77
+ false
78
+ end
79
+
80
+ def check_contents(actual)
81
+ return true if @contents_matchers.empty?
82
+
83
+ has_error = @contents_matchers.detect do |contents_matcher|
84
+ if contents_matcher.is_a?(Hash) || rspec_matcher?(contents_matcher)
85
+ verify_contents_using_regular_matcher!(contents_matcher, actual)
86
+ else
87
+ verify_contents_using_base_error!(contents_matcher, actual)
88
+ end
89
+ end
90
+
91
+ !has_error
92
+ end
93
+
94
+ # Returns true when error was detected
95
+ def verify_contents_using_regular_matcher!(contents_matcher, actual)
96
+ return false if values_match?(contents_matcher, actual.value.to_h)
97
+
98
+ @failure_message = "Contents in #{actual.value.class} do not match"
99
+ @expected = contents_matcher
100
+ @actual = actual.value.to_h
101
+
102
+ true
103
+ end
104
+
105
+ # Returns true when error was detected
106
+ def verify_contents_using_base_error!(contents_matcher, actual)
107
+ # Compare hashes if possible, otherwise expect it to respond to `#matches?`.
108
+ contents_matcher = contents_matcher.to_h if contents_matcher.respond_to?(:to_h)
109
+
110
+ return false if values_match?(contents_matcher, actual.value[:base_error])
111
+
112
+ @failure_message = "Expected failure to be based on #{contents_matcher}, but got #{actual.value.inspect}"
113
+ @expected = contents_matcher
114
+ @actual = actual.value[:base_error].to_h
115
+
116
+ true
117
+ end
118
+
119
+ def rspec_matcher?(matcher)
120
+ # Didn't find a better way to detect matchers like HashIncludingMatcher
121
+ matcher.class.to_s.start_with?('RSpec')
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/expectations'
4
+
5
+ module Salestation
6
+ module RSpec
7
+ class GliaInputValidationErrorMatcher
8
+ include ::RSpec::Matchers::Composable
9
+
10
+ attr_reader :expected, :actual, :failure_message
11
+
12
+ def initialize
13
+ @fields = []
14
+ @field_error_types = {}
15
+ end
16
+
17
+ def on(field)
18
+ @fields << field
19
+ self
20
+ end
21
+
22
+ def with_type(*types)
23
+ @field_error_types[@fields.last] = types
24
+ self
25
+ end
26
+
27
+ def matches?(actual)
28
+ @fields.all? do |field|
29
+ check_field_exists(field, actual) &&
30
+ check_field_error_types(field, actual)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def check_field_exists(field, actual)
37
+ actual[:error_details].key?(field)
38
+ end
39
+
40
+ def check_field_error_types(field, actual)
41
+ return true unless @field_error_types[field]
42
+
43
+ actual_error_types = actual[:error_details].fetch(field).map { |f| f.fetch(:type) }
44
+
45
+ (@field_error_types[field] - actual_error_types).empty?
46
+ end
47
+ end
48
+ end
49
+ end
@@ -8,7 +8,12 @@ require 'json'
8
8
  module Salestation
9
9
  class Web < Module
10
10
  module Types
11
- include Dry::Types()
11
+ dry_types_version = Gem.loaded_specs['dry-types'].version
12
+ if dry_types_version < Gem::Version.new('0.15.0')
13
+ include Dry::Types.module
14
+ else
15
+ include Dry::Types()
16
+ end
12
17
  end
13
18
 
14
19
  def initialize(errors: {})
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'http/accept'
4
+
3
5
  module Salestation
4
6
  class Web < Module
5
7
  module InputValidators
@@ -15,9 +17,11 @@ module Salestation
15
17
  end
16
18
 
17
19
  def call(header_value)
18
- header_valid = @allowed_headers.empty? || @allowed_headers.include?(header_value)
20
+ return Success(nil) if @allowed_headers.empty?
21
+
22
+ mime_types = HTTP::Accept::MediaTypes.parse(header_value).map(&:mime_type)
19
23
 
20
- if header_valid
24
+ if (@allowed_headers & mime_types).any?
21
25
  Success(nil)
22
26
  else
23
27
  Failure(App::Errors::NotAcceptable.new(
@@ -31,15 +31,23 @@ module Salestation
31
31
 
32
32
  class Error < Response
33
33
  attribute :status, Types::Strict::Integer
34
- attribute :message, Types::Strict::String
35
- attribute :debug_message, Types::Coercible::String.default('')
34
+ attribute? :message, Types::String.optional
35
+ attribute? :debug_message, Types::Coercible::String.default('')
36
36
  attribute :context, Types::Hash.default({}.freeze)
37
37
  attribute :headers, Types::Hash.default({}.freeze)
38
38
  attribute? :base_error, Types::Coercible::Hash
39
39
 
40
40
  def body
41
41
  # Merge into `base_error` to ensure standard fields are not overriden
42
- (base_error || {}).merge(message: message, debug_message: debug_message)
42
+ merge_not_nil(base_error || {}, :message, message)
43
+ .merge(debug_message: debug_message)
44
+ end
45
+
46
+ private
47
+
48
+ def merge_not_nil(map, key, value)
49
+ map[key] = value if value
50
+ map
43
51
  end
44
52
  end
45
53
 
@@ -58,9 +66,9 @@ module Salestation
58
66
  end
59
67
 
60
68
  class UnprocessableEntityFromSchemaErrors
61
- def self.create(errors:, hints:, base_error: nil, form_errors: false)
62
- message = parse_errors(errors)
63
- debug_message = parse_hints(hints)
69
+ def self.create(errors: nil, hints: nil, base_error: nil, form_errors: false)
70
+ message = errors ? parse_errors(errors) : nil
71
+ debug_message = hints ? parse_hints(hints) : nil
64
72
 
65
73
  UnprocessableEntity.new(
66
74
  message: message,
data/salestation.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "salestation"
7
- spec.version = "4.0.2"
7
+ spec.version = "4.3.1"
8
8
  spec.authors = ["Glia TechMovers"]
9
9
  spec.email = ["techmovers@glia.com"]
10
10
 
@@ -23,8 +23,11 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rake", "~> 13.0"
24
24
  spec.add_development_dependency "rspec", "~> 3.0"
25
25
  spec.add_development_dependency "pry", "~> 0.10.4"
26
+ spec.add_development_dependency "glia-errors", "~> 0.8"
27
+ spec.add_development_dependency "dry-validation"
26
28
 
27
29
  spec.add_dependency 'deterministic'
28
30
  spec.add_dependency 'dry-struct'
29
31
  spec.add_dependency 'dry-types'
32
+ spec.add_dependency 'http-accept', '~> 2.1'
30
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salestation
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 4.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Glia TechMovers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-26 00:00:00.000000000 Z
11
+ date: 2021-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,34 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 0.10.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: glia-errors
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: dry-validation
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
69
97
  - !ruby/object:Gem::Dependency
70
98
  name: deterministic
71
99
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +136,20 @@ dependencies:
108
136
  - - ">="
109
137
  - !ruby/object:Gem::Version
110
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: http-accept
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2.1'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2.1'
111
153
  description: ''
112
154
  email:
113
155
  - techmovers@glia.com
@@ -128,6 +170,9 @@ files:
128
170
  - lib/salestation/app/input_verification.rb
129
171
  - lib/salestation/app/request.rb
130
172
  - lib/salestation/result_helper.rb
173
+ - lib/salestation/rspec.rb
174
+ - lib/salestation/rspec/failure_matcher.rb
175
+ - lib/salestation/rspec/glia_input_validation_error_matcher.rb
131
176
  - lib/salestation/web.rb
132
177
  - lib/salestation/web/active_record_connection_management.rb
133
178
  - lib/salestation/web/error_mapper.rb