nobrainer-rspec 1.0.1
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 +7 -0
- data/CHANGELOG.md +37 -0
- data/Gemfile +5 -0
- data/LICENSE +20 -0
- data/README.md +61 -0
- data/Rakefile +8 -0
- data/bin/console +19 -0
- data/bin/setup +7 -0
- data/lib/matchers/associations.rb +287 -0
- data/lib/matchers/be_nobrainer_document.rb +28 -0
- data/lib/matchers/have_field.rb +484 -0
- data/lib/matchers/have_index_for.rb +164 -0
- data/lib/matchers/have_timestamps.rb +38 -0
- data/lib/matchers/validations.rb +80 -0
- data/lib/matchers/validations/acceptance_of.rb +11 -0
- data/lib/matchers/validations/confirmation_of.rb +17 -0
- data/lib/matchers/validations/exclusion_of.rb +51 -0
- data/lib/matchers/validations/format_of.rb +73 -0
- data/lib/matchers/validations/inclusion_of.rb +51 -0
- data/lib/matchers/validations/length_of.rb +127 -0
- data/lib/matchers/validations/numericality_of.rb +92 -0
- data/lib/matchers/validations/presence_of.rb +11 -0
- data/lib/matchers/validations/uniqueness_of.rb +53 -0
- data/lib/nobrainer-rspec.rb +1 -0
- data/lib/nobrainer/rspec.rb +31 -0
- data/lib/nobrainer/rspec/version.rb +7 -0
- data/nobrainer-rspec.gemspec +47 -0
- metadata +158 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
module Matchers
|
5
|
+
module Validations
|
6
|
+
class ValidateLengthOfMatcher < HaveValidationMatcher
|
7
|
+
def initialize(name)
|
8
|
+
super(name, :length)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_maximum(value)
|
12
|
+
@maximum = value
|
13
|
+
self
|
14
|
+
end
|
15
|
+
alias less_than with_maximum
|
16
|
+
|
17
|
+
def with_minimum(value)
|
18
|
+
@minimum = value
|
19
|
+
self
|
20
|
+
end
|
21
|
+
alias greater_than with_minimum
|
22
|
+
|
23
|
+
def within(value)
|
24
|
+
@within = value
|
25
|
+
self
|
26
|
+
end
|
27
|
+
alias in within
|
28
|
+
|
29
|
+
def as_exactly(value)
|
30
|
+
@is = value
|
31
|
+
self
|
32
|
+
end
|
33
|
+
alias is as_exactly
|
34
|
+
|
35
|
+
def matches?(actual)
|
36
|
+
return false unless @result = super(actual)
|
37
|
+
|
38
|
+
check_maximum if @maximum
|
39
|
+
check_minimum if @minimum
|
40
|
+
check_range if @within
|
41
|
+
check_exact if @is
|
42
|
+
|
43
|
+
@result
|
44
|
+
end
|
45
|
+
|
46
|
+
def description
|
47
|
+
options_desc = []
|
48
|
+
options_desc << "with minimum of #{@minimum}" if @minimum
|
49
|
+
options_desc << "with maximum of #{@maximum}" if @maximum
|
50
|
+
options_desc << "within the range of #{@within}" if @within
|
51
|
+
options_desc << "as exactly #{@is}" if @is
|
52
|
+
"#{super} #{options_desc.to_sentence}"
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def check_maximum
|
58
|
+
if actual_max.nil?
|
59
|
+
@negative_result_message += ' with no maximum'
|
60
|
+
@result = false
|
61
|
+
elsif actual_max == @maximum
|
62
|
+
@positive_result_message += " with maximum of #{@maximum}"
|
63
|
+
else
|
64
|
+
@negative_result_message += " with maximum of #{actual_max}"
|
65
|
+
@result = false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_minimum
|
70
|
+
if actual_min.nil?
|
71
|
+
@negative_result_message += ' with no minimum'
|
72
|
+
@result = false
|
73
|
+
elsif actual_min == @minimum
|
74
|
+
@positive_result_message += " with minimum of #{@minimum}"
|
75
|
+
else
|
76
|
+
@negative_result_message += " with minimum of #{actual_min}"
|
77
|
+
@result = false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_range
|
82
|
+
min, max = @within.minmax
|
83
|
+
if !actual_min.nil? && actual_max.nil?
|
84
|
+
@negative_result_message += " with no minimum but with maximum of #{actual_max}"
|
85
|
+
@result = false
|
86
|
+
elsif actual_min.nil? && !actual_max.nil?
|
87
|
+
@negative_result_message += " with minimum_of #{actual_min} but no maximum"
|
88
|
+
@result = false
|
89
|
+
elsif actual_min.nil? && actual_max.nil?
|
90
|
+
@negative_result_message += ' with no minimum and maximum'
|
91
|
+
@result = false
|
92
|
+
elsif actual_min == min && actual_max == max
|
93
|
+
@positive_result_message += " within the range of #{@within.inspect}"
|
94
|
+
else
|
95
|
+
@negative_result_message += " within the range of #{(actual_min..actual_max).inspect}"
|
96
|
+
@result = false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def check_exact
|
101
|
+
if actual_is == @is
|
102
|
+
@positive_result_message += " as exactly #{@is}"
|
103
|
+
else
|
104
|
+
@negative_result_message += " as exactly #{actual_is}"
|
105
|
+
@result = false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def actual_is
|
110
|
+
actual_is = @validator.options[:is]
|
111
|
+
end
|
112
|
+
|
113
|
+
def actual_min
|
114
|
+
@validator.options[:minimum] || (@validator.options[:in] || @validator.options[:within]).try(&:min)
|
115
|
+
end
|
116
|
+
|
117
|
+
def actual_max
|
118
|
+
@validator.options[:maximum] || (@validator.options[:in] || @validator.options[:within]).try(&:max)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def validate_length_of(field)
|
123
|
+
ValidateLengthOfMatcher.new(field)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
module Matchers
|
5
|
+
module Validations
|
6
|
+
class ValidateNumericalityOfMatcher < HaveValidationMatcher
|
7
|
+
ALLOWED_OPTIONS =
|
8
|
+
%i[
|
9
|
+
allow_nil
|
10
|
+
equal_to
|
11
|
+
even
|
12
|
+
greater_than
|
13
|
+
greater_than_or_equal_to
|
14
|
+
less_than
|
15
|
+
less_than_or_equal_to
|
16
|
+
nil
|
17
|
+
odd
|
18
|
+
only_integer
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
def initialize(field)
|
22
|
+
super(field, :numericality)
|
23
|
+
@options = {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_allow(options)
|
27
|
+
options[:equal_to] = options if options.is_a?(Numeric)
|
28
|
+
options[:allow_nil] = options.delete(:nil) if options.key?(:nil)
|
29
|
+
|
30
|
+
if !options.is_a?(Hash) || options.empty? || (options.keys - ALLOWED_OPTIONS).any?
|
31
|
+
message =
|
32
|
+
'validate_numericality_of#to_allow requires a Hash parameter containing' \
|
33
|
+
"any of the following keys: #{ALLOWED_OPTIONS.map(&:inspect).join(', ')}"
|
34
|
+
raise ArgumentError, message
|
35
|
+
end
|
36
|
+
|
37
|
+
@options.merge!(options)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def matches?(actual)
|
42
|
+
return false unless result = super(actual)
|
43
|
+
|
44
|
+
@options.each do |comparator, expected_value|
|
45
|
+
result &= (@validator.options[comparator] == expected_value)
|
46
|
+
end
|
47
|
+
|
48
|
+
@positive_result_message += options_message(@validator.options)
|
49
|
+
@negative_result_message += options_message(@validator.options)
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
def description
|
54
|
+
"#{super}#{options_message(@options)}"
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def options_message(options)
|
60
|
+
type_msg = []
|
61
|
+
comp_msg = []
|
62
|
+
options.each_pair do |key, value|
|
63
|
+
case key
|
64
|
+
when :allow_nil
|
65
|
+
when :only_integer
|
66
|
+
type_msg << 'integer' if value
|
67
|
+
when :odd, :even
|
68
|
+
type_msg << "#{key}-numbered" if value
|
69
|
+
else
|
70
|
+
comp_msg << "#{key.to_s.tr('_', ' ')} #{value.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
allow_nil = (options[:allow_nil] ? 'nil' : 'non-nil') if options.key?(:allow_nil)
|
74
|
+
['', 'allowing', allow_nil, type_msg.any? ? type_msg.to_sentence : nil, 'values', comp_msg.any? ? comp_msg.to_sentence : nil].compact.join(' ')
|
75
|
+
end
|
76
|
+
|
77
|
+
def method_missing(m, *args, &block)
|
78
|
+
if ALLOWED_OPTIONS.include?(m.to_sym)
|
79
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" if args.length > 1
|
80
|
+
send :to_allow, m.to_sym => args.first
|
81
|
+
else
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate_numericality_of(field)
|
88
|
+
ValidateNumericalityOfMatcher.new(field)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NoBrainer
|
4
|
+
module Matchers
|
5
|
+
module Validations
|
6
|
+
class ValidateUniquenessOfMatcher < HaveValidationMatcher
|
7
|
+
def initialize(field)
|
8
|
+
super(field, :uniqueness)
|
9
|
+
end
|
10
|
+
|
11
|
+
def scoped_to(*scope)
|
12
|
+
@scope = [scope].flatten.map(&:to_sym)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
alias scoped_on scoped_to
|
16
|
+
|
17
|
+
def matches?(actual)
|
18
|
+
return false unless @result = super(actual)
|
19
|
+
|
20
|
+
check_scope if @scope
|
21
|
+
|
22
|
+
@result
|
23
|
+
end
|
24
|
+
|
25
|
+
def description
|
26
|
+
options_desc = []
|
27
|
+
options_desc << " scoped to #{@scope.inspect}" if @scope
|
28
|
+
"#{super}#{options_desc.to_sentence}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def check_scope
|
34
|
+
message = " scope to #{@validator.options[:scope]}"
|
35
|
+
if @validator.options[:scope]
|
36
|
+
if [@validator.options[:scope] || ''].flatten.map(&:to_sym) == @scope
|
37
|
+
@positive_result_message += message
|
38
|
+
else
|
39
|
+
@negative_result_message += message
|
40
|
+
end
|
41
|
+
else
|
42
|
+
@negative_result_message += ' without a scope'
|
43
|
+
@result = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate_uniqueness_of(field)
|
49
|
+
ValidateUniquenessOfMatcher.new(field)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'nobrainer/rspec'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'nobrainer'
|
6
|
+
require 'rspec/core'
|
7
|
+
require 'rspec/expectations'
|
8
|
+
require 'rspec/mocks'
|
9
|
+
|
10
|
+
require 'matchers/associations'
|
11
|
+
require 'matchers/be_nobrainer_document'
|
12
|
+
require 'matchers/have_field'
|
13
|
+
require 'matchers/have_index_for'
|
14
|
+
require 'matchers/have_timestamps'
|
15
|
+
require 'matchers/validations'
|
16
|
+
require 'matchers/validations/acceptance_of'
|
17
|
+
require 'matchers/validations/confirmation_of'
|
18
|
+
require 'matchers/validations/exclusion_of'
|
19
|
+
require 'matchers/validations/format_of'
|
20
|
+
require 'matchers/validations/inclusion_of'
|
21
|
+
require 'matchers/validations/length_of'
|
22
|
+
require 'matchers/validations/numericality_of'
|
23
|
+
require 'matchers/validations/presence_of'
|
24
|
+
require 'matchers/validations/uniqueness_of'
|
25
|
+
|
26
|
+
module NoBrainer
|
27
|
+
module Matchers
|
28
|
+
include NoBrainer::Matchers::Associations
|
29
|
+
include NoBrainer::Matchers::Validations
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
|
5
|
+
require 'nobrainer/rspec/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'nobrainer-rspec'
|
9
|
+
spec.version = NoBrainer::RSpec::VERSION
|
10
|
+
spec.platform = Gem::Platform::RUBY
|
11
|
+
spec.authors = ['Guillaume Hain']
|
12
|
+
spec.email = ['zedtux@zedroot.org']
|
13
|
+
spec.homepage = 'https://gitlab.com/zedtux/nobrainer-rspec'
|
14
|
+
spec.summary = 'RSpec matchers for Nobrainer'
|
15
|
+
spec.description = 'RSpec matches for Nobrainer models, including ' \
|
16
|
+
'association and validation matchers.'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
|
19
|
+
spec.required_ruby_version = '>= 2.2'
|
20
|
+
|
21
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
22
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
23
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
24
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/-/blob/master/CHANGELOG.md"
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{
|
28
|
+
^(test|spec|features)/|
|
29
|
+
\.gitlab-ci.yml|
|
30
|
+
\.rspec|
|
31
|
+
\.gitignore|
|
32
|
+
Earthfile|
|
33
|
+
Dockerfile|
|
34
|
+
docker-compose.yml
|
35
|
+
}x)
|
36
|
+
end
|
37
|
+
spec.bindir = 'exe'
|
38
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
39
|
+
spec.require_paths = ['lib']
|
40
|
+
|
41
|
+
spec.add_dependency 'nobrainer'
|
42
|
+
spec.add_dependency 'rspec-core', '~> 3.3'
|
43
|
+
spec.add_dependency 'rspec-expectations', '~> 3.3'
|
44
|
+
spec.add_dependency 'rspec-mocks', '~> 3.3'
|
45
|
+
spec.add_development_dependency 'appraisal', '~> 2.2'
|
46
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nobrainer-rspec
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Guillaume Hain
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nobrainer
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.3'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-mocks
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.3'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: appraisal
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
description: RSpec matches for Nobrainer models, including association and validation
|
98
|
+
matchers.
|
99
|
+
email:
|
100
|
+
- zedtux@zedroot.org
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- CHANGELOG.md
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- bin/console
|
111
|
+
- bin/setup
|
112
|
+
- lib/matchers/associations.rb
|
113
|
+
- lib/matchers/be_nobrainer_document.rb
|
114
|
+
- lib/matchers/have_field.rb
|
115
|
+
- lib/matchers/have_index_for.rb
|
116
|
+
- lib/matchers/have_timestamps.rb
|
117
|
+
- lib/matchers/validations.rb
|
118
|
+
- lib/matchers/validations/acceptance_of.rb
|
119
|
+
- lib/matchers/validations/confirmation_of.rb
|
120
|
+
- lib/matchers/validations/exclusion_of.rb
|
121
|
+
- lib/matchers/validations/format_of.rb
|
122
|
+
- lib/matchers/validations/inclusion_of.rb
|
123
|
+
- lib/matchers/validations/length_of.rb
|
124
|
+
- lib/matchers/validations/numericality_of.rb
|
125
|
+
- lib/matchers/validations/presence_of.rb
|
126
|
+
- lib/matchers/validations/uniqueness_of.rb
|
127
|
+
- lib/nobrainer-rspec.rb
|
128
|
+
- lib/nobrainer/rspec.rb
|
129
|
+
- lib/nobrainer/rspec/version.rb
|
130
|
+
- nobrainer-rspec.gemspec
|
131
|
+
homepage: https://gitlab.com/zedtux/nobrainer-rspec
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata:
|
135
|
+
allowed_push_host: https://rubygems.org
|
136
|
+
homepage_uri: https://gitlab.com/zedtux/nobrainer-rspec
|
137
|
+
source_code_uri: https://gitlab.com/zedtux/nobrainer-rspec
|
138
|
+
changelog_uri: https://gitlab.com/zedtux/nobrainer-rspec/-/blob/master/CHANGELOG.md
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '2.2'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubygems_version: 3.1.4
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: RSpec matchers for Nobrainer
|
158
|
+
test_files: []
|