radius-spec 0.4.0 → 0.8.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.
@@ -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