radius-spec 0.4.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -268,6 +268,7 @@ module Radius
268
268
  def safe_transform(value)
269
269
  return value.call if value.is_a?(Proc)
270
270
  return value if value.frozen?
271
+
271
272
  value.dup
272
273
  end
273
274
 
@@ -426,53 +427,63 @@ module Radius
426
427
 
427
428
  # Convenience wrapper for building, and persisting, a model template.
428
429
  #
429
- # This is a thin wrapper around `build(name, attrs).tap(&:save!)`. The
430
- # persistence message `save!` will only be called on objects which
431
- # respond to it.
432
- #
433
- # ### Avoid for New Code
430
+ # This is a thin wrapper around:
434
431
  #
435
- # It is strongly suggested that you avoid using `create` for new code.
436
- # Instead be explicit about when and how objects are persisted. This
437
- # allows you to have fine grain control over how your data is setup.
432
+ # ```ruby
433
+ # build(name, attrs, &block).tap(&:save!)
434
+ # ```
438
435
  #
439
- # We suggest that you create instances which need to be persisted before
440
- # your specs using the following syntax:
436
+ # The persistence message `save!` will only be called on objects which
437
+ # respond to it.
441
438
  #
442
- # ```ruby
443
- # let(:an_instance) { build("AnyClass") }
439
+ # @note It is generally suggested that you avoid using `build!` for new
440
+ # code. Instead be explicit about when and how objects are persisted.
441
+ # This allows you to have fine grain control over how your data is
442
+ # setup.
444
443
  #
445
- # before do
446
- # an_instance.save!
447
- # end
448
- # ```
444
+ # We suggest that you create instances which need to be persisted
445
+ # before your specs using the following syntax:
449
446
  #
450
- # Alternatively if you really want for the instance be lazy instantiated,
451
- # and persisted, pass the appropriate persistence method as the block:
447
+ # ```ruby
448
+ # let(:an_instance) { build("AnyClass") }
452
449
  #
453
- # ```ruby
454
- # let(:an_instance) { build("AnyClass", &:save!) }
455
- # ```
450
+ # before do
451
+ # an_instance.save!
452
+ # end
453
+ # ```
456
454
  #
457
455
  # @param (see .build)
458
456
  # @return (see .build)
459
457
  # @raise (see .build)
460
458
  # @see .build
461
459
  # @see .define_factory
462
- def create(name, custom_attrs = {}, &block)
460
+ # @since 0.5.0
461
+ def build!(name, custom_attrs = {}, &block)
463
462
  instance = build(name, custom_attrs, &block)
464
463
  instance.save! if instance.respond_to?(:save!)
465
464
  instance
466
465
  end
466
+
467
+ # Legacy helper provided for backwards compatibility support.
468
+ #
469
+ # This provides the same behavior as {.build!} and will be removed in a
470
+ # future release.
471
+ #
472
+ # @param (see .build)
473
+ # @return (see .build)
474
+ # @raise (see .build)
475
+ # @see .build
476
+ # @see .define_factory
477
+ def create(name, custom_attrs = {}, &block)
478
+ build!(name, custom_attrs, &block)
479
+ end
467
480
  end
468
481
  end
469
482
  end
470
483
 
471
484
  # Try to load the factories defined for the specs
472
- # rubocop:disable Lint/HandleExceptions
473
485
  begin
474
486
  require 'support/model_factories'
475
487
  rescue LoadError
476
488
  # Ignore as this is an optional convenience feature
477
489
  end
478
- # rubocop:enable Lint/HandleExceptions
@@ -7,7 +7,7 @@ require 'rspec/rails'
7
7
 
8
8
  RSpec.configure do |config|
9
9
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
10
- config.fixture_path = ::Rails.root.join("spec", "fixtures")
10
+ config.fixture_path = ::Rails.root.join("spec", "fixtures").to_path
11
11
 
12
12
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
13
13
  # examples within a transaction, remove the following line or assign false
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Define negated matchers for use with composable matchers and compound
4
+ # expectations.
5
+ #
6
+ # @see https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/composing-matchers
7
+ # @see https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/compound-expectations
8
+ RSpec::Matchers.define_negated_matcher :exclude, :include
9
+ RSpec::Matchers.define_negated_matcher :excluding, :including
10
+ RSpec::Matchers.define_negated_matcher :not_eq, :eq
11
+
12
+ # Allows us to check that a block doesn't raise an exception while also
13
+ # checking for other changes using compound expectations; since you can't chain
14
+ # a negative (`not_to`) with any other matchers
15
+ #
16
+ # @see https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/compound-expectations
17
+ RSpec::Matchers.define_negated_matcher :not_change, :change
18
+ RSpec::Matchers.define_negated_matcher :not_raise_error, :raise_error
19
+ RSpec::Matchers.define_negated_matcher :not_raise_exception, :raise_exception
@@ -109,6 +109,11 @@ RSpec.configure do |config|
109
109
  config.include Radius::Spec::ModelFactory, :model_factory, :model_factories
110
110
  end
111
111
 
112
+ config.when_first_matching_example_defined(:tempfile, :tmpfile) do
113
+ require 'radius/spec/tempfile'
114
+ config.include Radius::Spec::Tempfile, :tempfile, :tmpfile
115
+ end
116
+
112
117
  config.when_first_matching_example_defined(type: :controller) do
113
118
  require 'radius/spec/model_factory'
114
119
  config.include Radius::Spec::ModelFactory, type: :controller
@@ -124,6 +129,11 @@ RSpec.configure do |config|
124
129
  config.include Radius::Spec::ModelFactory, type: :job
125
130
  end
126
131
 
132
+ config.when_first_matching_example_defined(type: :mailer) do
133
+ require 'radius/spec/model_factory'
134
+ config.include Radius::Spec::ModelFactory, type: :mailer
135
+ end
136
+
127
137
  config.when_first_matching_example_defined(type: :model) do
128
138
  require 'radius/spec/model_factory'
129
139
  config.include Radius::Spec::ModelFactory, type: :model
@@ -138,4 +148,14 @@ RSpec.configure do |config|
138
148
  require 'radius/spec/model_factory'
139
149
  config.include Radius::Spec::ModelFactory, type: :system
140
150
  end
151
+
152
+ config.when_first_matching_example_defined(:webmock) do
153
+ require 'webmock/rspec'
154
+ end
155
+
156
+ config.when_first_matching_example_defined(:vcr, :vcr_record, :vcr_record_new) do
157
+ require 'radius/spec/vcr'
158
+ end
141
159
  end
160
+
161
+ require 'radius/spec/rspec/negated_matchers'
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'tempfile'
5
+
6
+ module Radius
7
+ module Spec
8
+ # Temporary file helpers
9
+ #
10
+ # These helpers are meant to ease the creation of temporary files to either
11
+ # stub the data out or provide a location for data to be saved then
12
+ # verified.
13
+ #
14
+ # In the case of file stubs, using these helpers allows you to co-locate
15
+ # the file data with the specs. This makes it easy for someone to read the
16
+ # spec and understand the test case; instead of having to find a fixture
17
+ # file and look at its data. This also makes it easy to change the data
18
+ # between specs, allowing them to focus on just what they need.
19
+ #
20
+ # To make these helpers available require them after the gem:
21
+ #
22
+ # ```ruby
23
+ # require 'radius/spec'
24
+ # require 'radius/spec/tempfile'
25
+ # ```
26
+ #
27
+ # ### Including Helpers in Specs
28
+ #
29
+ # There are multiple ways you can use these helpers. Which method you
30
+ # choose depends on how much perceived magic/syntactic sugar you want:
31
+ #
32
+ # - call the helpers directly on the module
33
+ # - manually include the helper methods in the specs
34
+ # - use metadata to auto load this feature and include it in the specs
35
+ #
36
+ # When using the metadata option you do not need to explicitly require the
37
+ # module. This gem registers metadata with the RSpec configuration when it
38
+ # loads and `RSpec` is defined. When the matching metadata is first used it
39
+ # will automatically require and include the helpers.
40
+ #
41
+ # Any of following metadata will include the factory helpers:
42
+ #
43
+ # - `:tempfile`
44
+ # - `:tmpfile`
45
+ #
46
+ # @example use a helper directly in specs
47
+ # require 'radius/spec/tempfile'
48
+ #
49
+ # RSpec.describe AnyClass do
50
+ # it "includes the file helpers" do
51
+ # Radius::Spec::Tempfile.using_tempfile do |pathname|
52
+ # code_under_test pathname
53
+ # expect(pathname.read).to eq "Any written data"
54
+ # end
55
+ # end
56
+ # end
57
+ # @example manually include the helpers
58
+ # require 'radius/spec/tempfile'
59
+ #
60
+ # RSpec.describe AnyClass do
61
+ # include Radius::Spec::Tempfile
62
+ # it "includes the file helpers" do
63
+ # using_tempfile do |pathname|
64
+ # code_under_test pathname
65
+ # expect(pathname.read).to eq "Any written data"
66
+ # end
67
+ # end
68
+ # end
69
+ # @example use metadata to auto include the helpers
70
+ # RSpec.describe AnyClass do
71
+ # it "includes the file helpers", :tempfile do
72
+ # using_tempfile do |pathname|
73
+ # code_under_test pathname
74
+ # expect(pathname.read).to eq "Any written data"
75
+ # end
76
+ # end
77
+ # end
78
+ # @since 0.5.0
79
+ module Tempfile
80
+ module_function
81
+
82
+ # Convenience wrapper for managaing temporary files.
83
+ #
84
+ # This creates a temporary file and yields its path to the provided
85
+ # block. When the block returns the temporary file is deleted.
86
+ #
87
+ # ### Optional Parameters
88
+ #
89
+ # The block is required. All other parameters are optional. All
90
+ # parameters except `data` are Ruby version dependent and will be
91
+ # forwarded directly to the stdlib's
92
+ # {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
93
+ # `Tempfile.create`}. The when the `data` parameter is provided it's
94
+ # contents will be written to the temporary file prior to yielding to the
95
+ # block.
96
+ #
97
+ # @example creating a tempfile to pass to code
98
+ # def write_hello_world(filepath)
99
+ # File.write filepath, "Hello World"
100
+ # end
101
+ #
102
+ # Radius::Spec::Tempfile.using_tempfile do |pathname|
103
+ # write_hello_world pathname
104
+ # end
105
+ # @example creating a file stub
106
+ # stub_data = "Any file stub data text."
107
+ # Radius::Spec::Tempfile.using_tempfile(data: stub_data) do |stubpath|
108
+ # # File.read(stubpath)
109
+ # # => "Any file stub data text."
110
+ # code_under_test stubpath
111
+ # end
112
+ # @example creating a file stub inline
113
+ # Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT) do |stubpath|
114
+ # Any file stub data text.
115
+ # TEXT
116
+ # # File.read(stubpath)
117
+ # # => "Any file stub data text.\n"
118
+ # code_under_test stubpath
119
+ # end
120
+ # @example creating a file stub inline without trailing newline
121
+ # Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT.chomp) do |stubpath|
122
+ # Any file stub data text.
123
+ # TEXT
124
+ # # File.read(stubpath)
125
+ # # => "Any file stub data text."
126
+ # code_under_test stubpath
127
+ # end
128
+ # @example writing binary data inline
129
+ # Radius::Spec::Tempfile.using_tempfile(encoding: Encoding::BINARY, data: <<~BIN.chomp) do |binpath|
130
+ # \xC8\x90\xC5\x9D\xE1\xB9\x95\xC4\x93\xC4\x89
131
+ # BIN
132
+ # # File.read(binpath)
133
+ # # => "Ȑŝṕēĉ"
134
+ # code_under_test binpath
135
+ # end
136
+ # @param args [Object] addition file creation options
137
+ #
138
+ # Passed directly to {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
139
+ # `Tempfile.create`}; see the stdlib docs for details on available
140
+ # options.
141
+ # @param data [String] stub data to write to the file before yielding
142
+ # @param kwargs [Hash{Symbol => Object}] addition file creation options
143
+ #
144
+ # Passed directly to {https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create
145
+ # `Tempfile.create`}; see the stdlib docs for details on available
146
+ # options.
147
+ # @yieldparam pathname [Pathname] {https://ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html path}
148
+ # of the created temporary file
149
+ # @note A block must be provided
150
+ # @see https://ruby-doc.org/stdlib/libdoc/pathname/rdoc/Pathname.html Pathname
151
+ # @see https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html Tempfile
152
+ def using_tempfile(*args, data: nil, **kwargs)
153
+ args << 'tmpfile' if args.empty?
154
+ ::Tempfile.create(*args, **kwargs) do |f|
155
+ f.write(data)
156
+ f.close
157
+ yield Pathname(f.path)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'webmock/rspec'
4
+ require 'vcr'
5
+
6
+ VCR.configure do |config|
7
+ config.cassette_library_dir = "spec/cassettes"
8
+ config.hook_into :webmock
9
+ config.configure_rspec_metadata!
10
+ config.ignore_localhost = true
11
+
12
+ record_mode = case
13
+ when RSpec.configuration.files_to_run.one?
14
+ # When developing new features we often run new specs in
15
+ # isolation as we write the code. This is the time to allow
16
+ # creating the cassettes.
17
+ :once
18
+ when ENV['CI']
19
+ # Never let CI record
20
+ :none
21
+ else
22
+ # Default to blocking new requests to catch issues
23
+ :none
24
+ end
25
+ config.default_cassette_options = {
26
+ record: record_mode,
27
+
28
+ # Required for working proxy
29
+ update_content_length_header: true,
30
+
31
+ # Raise errors when recorded cassettes no longer match interactions
32
+ allow_unused_http_interactions: false,
33
+ }
34
+
35
+ # Filter out common sensitive or environment specific data
36
+ %w[
37
+ AWS_ACCESS_KEY_ID
38
+ AWS_SECRET_ACCESS_KEY
39
+ GOOGLE_CLIENT_ID
40
+ GOOGLE_CLIENT_SECRET
41
+ RADIUS_OAUTH_PROVIDER_APP_ID
42
+ RADIUS_OAUTH_PROVIDER_APP_SECRET
43
+ RADIUS_OAUTH_PROVIDER_URL
44
+ ].each do |secret|
45
+ config.filter_sensitive_data("<#{secret}>") { ENV[secret] }
46
+
47
+ config.filter_sensitive_data("<#{secret}_FORM>") {
48
+ URI.encode_www_form_component(ENV[secret]) if ENV[secret]
49
+ }
50
+
51
+ config.filter_sensitive_data("<#{secret}_URI>") {
52
+ ERB::Util.url_encode(ENV[secret]) if ENV[secret]
53
+ }
54
+
55
+ config.filter_sensitive_data('<AUTHORIZATION_HEADER>') { |interaction|
56
+ interaction.request.headers['Authorization']&.first
57
+ }
58
+ end
59
+ end
60
+
61
+ RSpec.configure do |config|
62
+ {
63
+ vcr_record: :once,
64
+ vcr_record_new: :new_episodes,
65
+ }.each do |tag, record_mode|
66
+ config.define_derived_metadata(tag) do |metadata|
67
+ case metadata[:vcr]
68
+ when nil, true
69
+ metadata[:vcr] = { record: record_mode }
70
+ when Hash
71
+ metadata[:vcr][:record] = record_mode
72
+ else
73
+ raise "Unknown VCR metadata value: #{metadata[:vcr].inspect}"
74
+ end
75
+ end
76
+ end
77
+
78
+ config.define_derived_metadata(:focus) do |metadata|
79
+ # VCR is flagged as falsey
80
+ next if metadata.key?(:vcr) && !metadata[:vcr]
81
+
82
+ case metadata[:vcr]
83
+ when nil, true
84
+ metadata[:vcr] = { record: :once }
85
+ when Hash
86
+ metadata[:vcr][:record] ||= :once
87
+ else
88
+ raise "Unknown VCR metadata value: #{metadata[:vcr].inspect}"
89
+ end
90
+ end
91
+ end
92
+
93
+ # Try to any custom VCR config for the app
94
+ begin
95
+ require 'support/vcr'
96
+ rescue LoadError
97
+ # Ignore as this is an optional convenience feature
98
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Radius
4
4
  module Spec
5
- VERSION = "0.4.0"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end
data/radius-spec.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.metadata = {
14
14
  "bug_tracker_uri" => "https://github.com/RadiusNetworks/radius-spec/issues",
15
- "changelog_uri" => "https://github.com/RadiusNetworks/radius-spec/blob/v#{Radius::Spec::VERSION}/CHANGELOG.md",
15
+ "changelog_uri" => "https://github.com/RadiusNetworks/radius-spec/blob/v#{Radius::Spec::VERSION}/CHANGELOG.md",
16
16
  "source_code_uri" => "https://github.com/RadiusNetworks/radius-spec/tree/v#{Radius::Spec::VERSION}",
17
17
  }
18
18
  spec.summary = "Radius Networks RSpec setup and plug-ins"
@@ -21,18 +21,19 @@ Gem::Specification.new do |spec|
21
21
  spec.homepage = "https://github.com/RadiusNetworks/radius-spec"
22
22
  spec.license = "Apache-2.0"
23
23
 
24
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
+ spec.files = `git ls-files -z`.split("\x0").reject { |f|
25
25
  f.match(%r{^(test|spec|features)/})
26
- end
26
+ }
27
27
  spec.bindir = "exe"
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.required_ruby_version = ">= 2.5"
31
+ spec.required_ruby_version = ">= 2.5" # rubocop:disable Gemspec/RequiredRubyVersion
32
32
 
33
33
  spec.add_runtime_dependency "rspec", "~> 3.7"
34
- spec.add_runtime_dependency "rubocop", "~> 0.58.1"
34
+ spec.add_runtime_dependency "rubocop", "~> 0.82.0"
35
+ spec.add_runtime_dependency "rubocop-rails", "~> 2.5.2"
35
36
 
36
- spec.add_development_dependency "bundler", "~> 1.16"
37
- spec.add_development_dependency "rake", "~> 12.0"
37
+ spec.add_development_dependency "bundler", ">= 2.2.10"
38
+ spec.add_development_dependency "rake", ">= 12.0", "< 14.0"
38
39
  end