servactory 2.5.0.rc2 → 2.5.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/servactory/install_generator.rb +21 -0
- data/lib/generators/servactory/rspec_generator.rb +88 -0
- data/lib/generators/servactory/service_generator.rb +49 -0
- data/lib/generators/servactory/templates/services/application_service/base.rb +60 -0
- data/lib/generators/servactory/templates/services/application_service/exceptions.rb +11 -0
- data/lib/generators/servactory/templates/services/application_service/result.rb +5 -0
- data/lib/servactory/context/workspace/internals.rb +1 -1
- data/lib/servactory/info/dsl.rb +56 -4
- data/lib/servactory/result.rb +1 -1
- data/lib/servactory/test_kit/rspec/helpers.rb +95 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +121 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +70 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +61 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +72 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +203 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +67 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +63 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +78 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +233 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +148 -0
- data/lib/servactory/test_kit/rspec/matchers.rb +295 -0
- data/lib/servactory/test_kit/utils/faker.rb +78 -0
- data/lib/servactory/version.rb +1 -1
- data/lib/servactory.rb +1 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e7661bf7b65a1dd0c2214be8364897c91522116fb208de82ac7869f4ec81534
|
4
|
+
data.tar.gz: f87706b5056118c9c772a08b9cd13047c79d121eb8dbdc21447e21677c4d1504
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62ff3f88d0df55e1a02761990cc0b624bb151ef0f7d5fe2f5e5f13f22934d88e50c036e97faa854b27b7a34bfb05fcef4d839e3cc4c0aed1b282613b93a37da5
|
7
|
+
data.tar.gz: 1f78163c75c54c7f6eaac73b9ceaa8b433964bfae3ba0d45fb0bc743a5a4d586f19453ccb1d4e67d67e8dbd3157d32d8b5c047405550775b6aab6976c0944e98
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/base"
|
4
|
+
|
5
|
+
module Servactory
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
def copy_services
|
11
|
+
directory "services/application_service", "app/services/application_service"
|
12
|
+
end
|
13
|
+
|
14
|
+
def copy_locales
|
15
|
+
%i[en ru].each do |locale|
|
16
|
+
copy_file "../../../../config/locales/#{locale}.yml", "config/locales/servactory.#{locale}.yml"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Servactory
|
6
|
+
module Generators
|
7
|
+
class RspecGenerator < Rails::Generators::NamedBase
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
argument :attributes, type: :array, default: [], banner: "input_name"
|
11
|
+
|
12
|
+
def create_service
|
13
|
+
create_file "app/services/#{file_path}_spec.rb" do
|
14
|
+
<<~RUBY
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
RSpec.describe #{class_name}, type: :service do
|
18
|
+
pending "add some examples to (or delete) \#{__FILE__}"
|
19
|
+
|
20
|
+
# let(:attributes) do
|
21
|
+
# {
|
22
|
+
#{input_attribute_draw}
|
23
|
+
# }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
#{input_let_draw}
|
27
|
+
#
|
28
|
+
# describe "validation" do
|
29
|
+
# describe "inputs" do
|
30
|
+
#{input_validation_draw}
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# describe "internals" do
|
34
|
+
# it { expect { perform }.to have_internal(:some_data).type(String) }
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# describe ".call!" do
|
39
|
+
# subject(:perform) { described_class.call!(**attributes) }
|
40
|
+
#
|
41
|
+
# describe "and the data required for work is also valid" do
|
42
|
+
# it { expect(perform).to be_success_service }
|
43
|
+
#
|
44
|
+
# # ...
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# describe "but the data required for work is invalid" do
|
48
|
+
# # Provide a reason why the data is invalid and then use this:
|
49
|
+
# it { expect(perform).to be_failure_service }
|
50
|
+
#
|
51
|
+
# # ...
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
end
|
55
|
+
RUBY
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def input_attribute_draw
|
60
|
+
input_names.map do |input_name|
|
61
|
+
<<~RUBY.strip
|
62
|
+
# #{input_name}: #{input_name}
|
63
|
+
RUBY
|
64
|
+
end.join(",\n ")
|
65
|
+
end
|
66
|
+
|
67
|
+
def input_let_draw
|
68
|
+
input_names.map do |input_name|
|
69
|
+
<<~RUBY.strip
|
70
|
+
# let(:#{input_name}) { "Some value" }
|
71
|
+
RUBY
|
72
|
+
end.join("\n ")
|
73
|
+
end
|
74
|
+
|
75
|
+
def input_validation_draw
|
76
|
+
input_names.map do |input_name|
|
77
|
+
<<~RUBY.strip
|
78
|
+
# it { expect { perform }.to have_input(:#{input_name}).valid_with(attributes).type(String).required }
|
79
|
+
RUBY
|
80
|
+
end.join("\n ")
|
81
|
+
end
|
82
|
+
|
83
|
+
def input_names
|
84
|
+
@input_names ||= attributes_names
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Servactory
|
6
|
+
module Generators
|
7
|
+
class ServiceGenerator < Rails::Generators::NamedBase
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
argument :attributes, type: :array, default: [], banner: "input_name"
|
11
|
+
|
12
|
+
def create_service # rubocop:disable Metrics/MethodLength
|
13
|
+
create_file "app/services/#{file_path}.rb" do
|
14
|
+
<<~RUBY
|
15
|
+
# frozen_string_literal: true
|
16
|
+
|
17
|
+
class #{class_name} < ApplicationService::Base
|
18
|
+
#{input_draw}
|
19
|
+
|
20
|
+
output :result, type: Symbol
|
21
|
+
|
22
|
+
make :something
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def something
|
27
|
+
# Write your code here
|
28
|
+
|
29
|
+
outputs.result = :done
|
30
|
+
end
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def input_draw
|
37
|
+
input_names.map do |input_name|
|
38
|
+
<<~RUBY.squish
|
39
|
+
input :#{input_name}, type: String
|
40
|
+
RUBY
|
41
|
+
end.join("\n ")
|
42
|
+
end
|
43
|
+
|
44
|
+
def input_names
|
45
|
+
@input_names ||= attributes_names
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApplicationService
|
4
|
+
class Base
|
5
|
+
include Servactory::DSL
|
6
|
+
|
7
|
+
# More information: https://servactory.com/guide/extensions
|
8
|
+
# include Servactory::DSL.with_extensions(
|
9
|
+
# ApplicationService::Extensions::YourExtension::DSL
|
10
|
+
# )
|
11
|
+
|
12
|
+
fail_on! ActiveRecord::RecordInvalid
|
13
|
+
|
14
|
+
# More information: https://servactory.com/guide/configuration
|
15
|
+
configuration do
|
16
|
+
input_exception_class ApplicationService::Exceptions::Input
|
17
|
+
internal_exception_class ApplicationService::Exceptions::Internal
|
18
|
+
output_exception_class ApplicationService::Exceptions::Output
|
19
|
+
|
20
|
+
failure_class ApplicationService::Exceptions::Failure
|
21
|
+
|
22
|
+
result_class ApplicationService::Result
|
23
|
+
|
24
|
+
# input_option_helpers(
|
25
|
+
# [
|
26
|
+
# Servactory::ToolKit::DynamicOptions::Format.use,
|
27
|
+
# Servactory::ToolKit::DynamicOptions::Min.use,
|
28
|
+
# Servactory::ToolKit::DynamicOptions::Max.use,
|
29
|
+
# ApplicationService::DynamicOptions::CustomEq.use
|
30
|
+
# ]
|
31
|
+
# )
|
32
|
+
|
33
|
+
# internal_option_helpers(
|
34
|
+
# [
|
35
|
+
# Servactory::ToolKit::DynamicOptions::Format.use,
|
36
|
+
# Servactory::ToolKit::DynamicOptions::Min.use,
|
37
|
+
# Servactory::ToolKit::DynamicOptions::Max.use,
|
38
|
+
# ApplicationService::DynamicOptions::CustomEq.use
|
39
|
+
# ]
|
40
|
+
# )
|
41
|
+
|
42
|
+
# output_option_helpers(
|
43
|
+
# [
|
44
|
+
# Servactory::ToolKit::DynamicOptions::Format.use,
|
45
|
+
# Servactory::ToolKit::DynamicOptions::Min.use,
|
46
|
+
# Servactory::ToolKit::DynamicOptions::Max.use,
|
47
|
+
# ApplicationService::DynamicOptions::CustomEq.use
|
48
|
+
# ]
|
49
|
+
# )
|
50
|
+
|
51
|
+
# collection_mode_class_names [ActiveRecord::Relation]
|
52
|
+
|
53
|
+
# hash_mode_class_names [CustomHash]
|
54
|
+
|
55
|
+
# action_shortcuts %i[assign build create save]
|
56
|
+
|
57
|
+
# action_aliases %i[do_it!]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ApplicationService
|
4
|
+
module Exceptions
|
5
|
+
class Input < Servactory::Exceptions::Input; end
|
6
|
+
class Output < Servactory::Exceptions::Output; end
|
7
|
+
class Internal < Servactory::Exceptions::Internal; end
|
8
|
+
|
9
|
+
class Failure < Servactory::Exceptions::Failure; end
|
10
|
+
end
|
11
|
+
end
|
data/lib/servactory/info/dsl.rb
CHANGED
@@ -8,11 +8,63 @@ module Servactory
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
|
-
def info
|
11
|
+
def info # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
12
12
|
Servactory::Info::Result.new(
|
13
|
-
inputs: collection_of_inputs.
|
14
|
-
|
15
|
-
|
13
|
+
inputs: collection_of_inputs.to_h do |input|
|
14
|
+
work = input.class::Work.new(input)
|
15
|
+
consists_of = input.collection_of_options.find_by(name: :consists_of)
|
16
|
+
inclusion = input.collection_of_options.find_by(name: :inclusion)
|
17
|
+
must = input.collection_of_options.find_by(name: :must)
|
18
|
+
|
19
|
+
[
|
20
|
+
input.name,
|
21
|
+
{
|
22
|
+
work: work,
|
23
|
+
types: input.types,
|
24
|
+
required: input.required,
|
25
|
+
default: input.default,
|
26
|
+
consists_of: consists_of.body,
|
27
|
+
inclusion: inclusion.body,
|
28
|
+
must: must.body
|
29
|
+
}
|
30
|
+
]
|
31
|
+
end,
|
32
|
+
|
33
|
+
internals: collection_of_internals.to_h do |internal|
|
34
|
+
work = internal.class::Work.new(internal)
|
35
|
+
consists_of = internal.collection_of_options.find_by(name: :consists_of)
|
36
|
+
inclusion = internal.collection_of_options.find_by(name: :inclusion)
|
37
|
+
must = internal.collection_of_options.find_by(name: :must)
|
38
|
+
|
39
|
+
[
|
40
|
+
internal.name,
|
41
|
+
{
|
42
|
+
work: work,
|
43
|
+
types: internal.types,
|
44
|
+
consists_of: consists_of.body,
|
45
|
+
inclusion: inclusion.body,
|
46
|
+
must: must.body
|
47
|
+
}
|
48
|
+
]
|
49
|
+
end,
|
50
|
+
|
51
|
+
outputs: collection_of_outputs.to_h do |output|
|
52
|
+
work = output.class::Work.new(output)
|
53
|
+
consists_of = output.collection_of_options.find_by(name: :consists_of)
|
54
|
+
inclusion = output.collection_of_options.find_by(name: :inclusion)
|
55
|
+
must = output.collection_of_options.find_by(name: :must)
|
56
|
+
|
57
|
+
[
|
58
|
+
output.name,
|
59
|
+
{
|
60
|
+
work: work,
|
61
|
+
types: output.types,
|
62
|
+
consists_of: consists_of.body,
|
63
|
+
inclusion: inclusion.body,
|
64
|
+
must: must.body
|
65
|
+
}
|
66
|
+
]
|
67
|
+
end
|
16
68
|
)
|
17
69
|
end
|
18
70
|
end
|
data/lib/servactory/result.rb
CHANGED
@@ -110,7 +110,7 @@ module Servactory
|
|
110
110
|
########################################################################
|
111
111
|
|
112
112
|
def rescue_no_method_error_with(exception:) # rubocop:disable Metrics/MethodLength
|
113
|
-
raise exception if @context.blank?
|
113
|
+
raise exception if @context.blank? || @context.instance_of?(Servactory::TestKit::Result)
|
114
114
|
|
115
115
|
raise @context.class.config.failure_class.new(
|
116
116
|
type: :base,
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Helpers
|
7
|
+
def allow_service_as_success!(service_class_name, &block)
|
8
|
+
allow_service!(service_class_name, :as_success, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def allow_service_as_success(service_class_name, &block)
|
12
|
+
allow_service(service_class_name, :as_success, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def allow_service_as_failure!(service_class_name, &block)
|
16
|
+
allow_service!(service_class_name, :as_failure, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
def allow_service_as_failure(service_class_name, &block)
|
20
|
+
allow_service(service_class_name, :as_failure, &block)
|
21
|
+
end
|
22
|
+
|
23
|
+
########################################################################
|
24
|
+
|
25
|
+
def allow_service!(service_class_name, result_type, &block)
|
26
|
+
allow_servactory(service_class_name, :call!, result_type, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def allow_service(service_class_name, result_type, &block)
|
30
|
+
allow_servactory(service_class_name, :call, result_type, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
########################################################################
|
34
|
+
|
35
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
36
|
+
def allow_servactory(service_class_name, method_call, result_type)
|
37
|
+
method_call = method_call.to_sym
|
38
|
+
result_type = result_type.to_sym
|
39
|
+
|
40
|
+
unless %i[call! call].include?(method_call)
|
41
|
+
raise ArgumentError, "Invalid value for `method_call`. Must be `:call!` or `:call`."
|
42
|
+
end
|
43
|
+
|
44
|
+
unless %i[as_success as_failure].include?(result_type)
|
45
|
+
raise ArgumentError, "Invalid value for `result_type`. Must be `:as_success` or `:as_failure`."
|
46
|
+
end
|
47
|
+
|
48
|
+
as_success = result_type == :as_success
|
49
|
+
with_bang = method_call == :call!
|
50
|
+
|
51
|
+
if block_given? && !yield.is_a?(Hash) && as_success
|
52
|
+
raise ArgumentError, "Invalid value for block. Must be a Hash with attributes."
|
53
|
+
end
|
54
|
+
|
55
|
+
and_return_or_raise = with_bang && !as_success ? :and_raise : :and_return
|
56
|
+
|
57
|
+
result = if block_given?
|
58
|
+
if yield.is_a?(Hash)
|
59
|
+
yield
|
60
|
+
else
|
61
|
+
{ with_bang ? :exception : :error => yield }
|
62
|
+
end
|
63
|
+
else
|
64
|
+
{}
|
65
|
+
end
|
66
|
+
|
67
|
+
# puts
|
68
|
+
# puts <<~RUBY
|
69
|
+
# allow(#{service_class_name}).to(
|
70
|
+
# receive(#{method_call.inspect})
|
71
|
+
# .public_send(
|
72
|
+
# #{and_return_or_raise.inspect},
|
73
|
+
# Servactory::TestKit::Result.public_send(#{result_type.inspect}, #{result})
|
74
|
+
# )
|
75
|
+
# )
|
76
|
+
# RUBY
|
77
|
+
# puts
|
78
|
+
|
79
|
+
allow(service_class_name).to(
|
80
|
+
receive(method_call)
|
81
|
+
.public_send(
|
82
|
+
and_return_or_raise,
|
83
|
+
if as_success
|
84
|
+
Servactory::TestKit::Result.public_send(result_type, **result)
|
85
|
+
else
|
86
|
+
Servactory::TestKit::Result.public_send(result_type, result)
|
87
|
+
end
|
88
|
+
)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class ConsistsOfMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, option_types, consists_of_types,
|
12
|
+
custom_message)
|
13
|
+
@described_class = described_class
|
14
|
+
@attribute_type = attribute_type
|
15
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
16
|
+
@attribute_name = attribute_name
|
17
|
+
@option_types = option_types
|
18
|
+
@consists_of_types = consists_of_types
|
19
|
+
@custom_message = custom_message
|
20
|
+
|
21
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
22
|
+
|
23
|
+
@missing_option = ""
|
24
|
+
end
|
25
|
+
|
26
|
+
def description
|
27
|
+
result = "consists_of: "
|
28
|
+
result + consists_of_types.join(", ")
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches?(subject)
|
32
|
+
if submatcher_passes?(subject)
|
33
|
+
true
|
34
|
+
else
|
35
|
+
@missing_option = build_missing_option
|
36
|
+
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :described_class,
|
44
|
+
:attribute_type,
|
45
|
+
:attribute_type_plural,
|
46
|
+
:attribute_name,
|
47
|
+
:option_types,
|
48
|
+
:consists_of_types,
|
49
|
+
:custom_message,
|
50
|
+
:attribute_data
|
51
|
+
|
52
|
+
def submatcher_passes?(_subject)
|
53
|
+
attribute_consists_of = Array(attribute_data.fetch(:consists_of).fetch(:type) || [])
|
54
|
+
|
55
|
+
matched = attribute_consists_of.difference(consists_of_types).empty?
|
56
|
+
|
57
|
+
matched &&= attribute_consists_of_message.casecmp(custom_message).zero? if custom_message.present?
|
58
|
+
|
59
|
+
matched
|
60
|
+
end
|
61
|
+
|
62
|
+
def attribute_consists_of_message # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
63
|
+
attribute_consists_of_message = attribute_data.fetch(:consists_of).fetch(:message)
|
64
|
+
|
65
|
+
if attribute_consists_of_message.nil?
|
66
|
+
I18n.t(
|
67
|
+
"servactory.#{attribute_type_plural}.validations.required.default_error.for_collection",
|
68
|
+
service_class_name: described_class.name,
|
69
|
+
"#{attribute_type}_name": attribute_name
|
70
|
+
)
|
71
|
+
elsif attribute_consists_of_message.is_a?(Proc)
|
72
|
+
input_work = attribute_data.fetch(:work)
|
73
|
+
|
74
|
+
attribute_consists_of_message.call(
|
75
|
+
input: input_work,
|
76
|
+
expected_type: String,
|
77
|
+
given_type: Servactory::TestKit::FakeType.new.class.name
|
78
|
+
)
|
79
|
+
else
|
80
|
+
attribute_consists_of_message
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def build_missing_option # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
85
|
+
attribute_consists_of = Array(attribute_data.fetch(:consists_of).fetch(:type) || [])
|
86
|
+
|
87
|
+
unless attribute_consists_of.difference(consists_of_types).empty?
|
88
|
+
text_about_types = option_types.size > 1 ? "the following types" : "type"
|
89
|
+
|
90
|
+
return <<~MESSAGE
|
91
|
+
should be a collection consisting of #{text_about_types}
|
92
|
+
|
93
|
+
expected #{consists_of_types.inspect}
|
94
|
+
got #{attribute_consists_of.inspect}
|
95
|
+
MESSAGE
|
96
|
+
end
|
97
|
+
|
98
|
+
if custom_message.present? && !attribute_consists_of_message.casecmp(custom_message).zero?
|
99
|
+
return <<~MESSAGE
|
100
|
+
should be a collection with a message
|
101
|
+
|
102
|
+
expected #{custom_message.inspect}
|
103
|
+
got #{attribute_consists_of_message.inspect}
|
104
|
+
MESSAGE
|
105
|
+
end
|
106
|
+
|
107
|
+
<<~MESSAGE
|
108
|
+
got an unexpected case when using `consists_of`
|
109
|
+
|
110
|
+
Please try to build an example based on the documentation.
|
111
|
+
Or report your problem to us:
|
112
|
+
|
113
|
+
https://github.com/servactory/servactory/issues
|
114
|
+
MESSAGE
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class InclusionMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, values)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@values = values
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"inclusion: #{values.join(', ')}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(subject)
|
28
|
+
if submatcher_passes?(subject)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
@missing_option = build_missing_option
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :described_class,
|
40
|
+
:attribute_type,
|
41
|
+
:attribute_type_plural,
|
42
|
+
:attribute_name,
|
43
|
+
:values,
|
44
|
+
:attribute_data
|
45
|
+
|
46
|
+
def submatcher_passes?(_subject)
|
47
|
+
attribute_inclusion = attribute_data.fetch(:inclusion)
|
48
|
+
attribute_inclusion_in = attribute_inclusion.fetch(:in)
|
49
|
+
|
50
|
+
attribute_inclusion_in.difference(values).empty? &&
|
51
|
+
values.difference(attribute_inclusion_in).empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_missing_option
|
55
|
+
attribute_inclusion = attribute_data.fetch(:inclusion)
|
56
|
+
attribute_inclusion_in = attribute_inclusion.fetch(:in)
|
57
|
+
|
58
|
+
<<~MESSAGE
|
59
|
+
should include the expected values
|
60
|
+
|
61
|
+
expected #{values.inspect}
|
62
|
+
got #{attribute_inclusion_in.inspect}
|
63
|
+
MESSAGE
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class MustMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, must_names)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@must_names = must_names
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"must: #{must_names.join(', ')}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(subject)
|
28
|
+
if submatcher_passes?(subject)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
@missing_option = build_missing_option
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :described_class,
|
40
|
+
:attribute_type,
|
41
|
+
:attribute_type_plural,
|
42
|
+
:attribute_name,
|
43
|
+
:must_names,
|
44
|
+
:attribute_data
|
45
|
+
|
46
|
+
def submatcher_passes?(_subject)
|
47
|
+
attribute_must = attribute_data.fetch(:must)
|
48
|
+
|
49
|
+
attribute_must.keys.difference(must_names).empty? &&
|
50
|
+
must_names.difference(attribute_must.keys).empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_missing_option
|
54
|
+
"should #{must_names.join(', ')}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|