radius-spec 0.4.0 → 0.5.0

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: 36a2686c3a5c78dd53ae5155196068a0c363b438edf1e84f64212ce8e7ffe905
4
- data.tar.gz: 3600656a4e89595218980fa536a785b94494e5396bb10ecf294cc3741f1a693b
3
+ metadata.gz: 229205333f0d39f679032b076188c7a18013408c034486de29c4f4a684ddcf74
4
+ data.tar.gz: 37aa800e6bfdb5b273de8837cbe62f87ca8f4f6b3a93bce091b7bbe6867cd64e
5
5
  SHA512:
6
- metadata.gz: a8b81229f457df7c5ea6e6d1984ff2d68323b6c1acab81c5c4ede9001ed97f7619f2b355de636f4e72560412185b13c2ec804fc78866d5dcef1e258cde58ddee
7
- data.tar.gz: c48a00ab090ace41b513c2007e55ece0aa8b4341bf0747f3aad5f0d11bf11c6745f64fca02d5150ff15f0f72cf1dafc49ed95985b4e744a5f291ea5e247f1eb2
6
+ metadata.gz: a0dde94d3cb6579e8faf68027cf1f77c1d8268d4d7c1979b05c5554e4e000769e2b6240f398d396558d0c71c2c717ead0062c5f236d4d03a49981fa91c8a95ca
7
+ data.tar.gz: 027ef0ac9fcbc76dbaa374382035829e7607c4d3dc2979f2bbc8413b7cbd032961760ed798ac1bda7b83fba9899127c82c96743e8d716fca50f2fb93e01bb0bd
data/.yardopts CHANGED
@@ -1,4 +1,5 @@
1
1
  --markup markdown
2
+ --markup-provider redcarpet
2
3
  --no-private
3
4
  -
4
5
  CHANGELOG.md
data/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## 0.5.0 (September 26, 2018)
2
+
3
+ [Full Changelog](https://github.com/RadiusNetworks/radius-spec/compare/v0.4.0...v0.5.0)
4
+
5
+ ### Enhancements
6
+
7
+ - Add common VCR configuration (Aaron Kromer, #16)
8
+ - Filters out `Authorization` headers
9
+ - Filters out the following sensitive/environment varying `ENV` values, as
10
+ well as their URL and form encoded variants:
11
+ - `AWS_ACCESS_KEY_ID`
12
+ - `AWS_SECRET_ACCESS_KEY`
13
+ - `GOOGLE_CLIENT_ID`
14
+ - `GOOGLE_CLIENT_SECRET`
15
+ - `RADIUS_OAUTH_PROVIDER_APP_ID`
16
+ - `RADIUS_OAUTH_PROVIDER_APP_SECRET`
17
+ - `RADIUS_OAUTH_PROVIDER_URL`
18
+ - Add "temp file" helpers for working with file stubs (Aaron Kromer, #15)
19
+ - Upgrade to Rubocop 0.59.x (Aaron Kromer, #14)
20
+ - Adjust common Rubocop configuration (Aaron Kromer, #14)
21
+ - `Layout/EmptyLineAfterGuardClause` is enabled by default
22
+ - Enable `Rails/SaveBang` to highlight potential lurking issues
23
+ - Expand `Rails/FindBy` and `Rails/FindEach` to check all `/app` and `/lib`
24
+ - Add more functional methods
25
+ - `default_scope`
26
+ - `filter_sensitive_data`
27
+ - Add `build!` factory method to compliment `build` to help resolving Rubocop
28
+ violations for `Rails/SaveBang` (Aaron Kromer, #14)
29
+ - Load model factory for specs tagged with 'type: :mailer' (Aaron Kromer, #11)
30
+ - Include the following negated RSpec matchers (Aaron Kromer, #12)
31
+ - `exclude` / `excluding`
32
+ - `not_eq`
33
+ - `not_change`
34
+ - `not_raise_error` / `not_raise_exception`
35
+
36
+ ### Bug Fixes
37
+
38
+ - Fix `NoMethodError: undefined method 'strip'` when the fixture path is a
39
+ `Pathname` object (Aaron Kromer, #13)
40
+
41
+
1
42
  ## 0.4.0 (July 10, 2018)
2
43
 
3
44
  [Full Changelog](https://github.com/RadiusNetworks/radius-spec/compare/v0.3.0...v0.4.0)
@@ -88,7 +129,7 @@
88
129
 
89
130
  ### Bug Fixes
90
131
 
91
- - Fix `NameError: undefined local variable or method `config` for Rails RSpec
132
+ - Fix `NameError: undefined local variable or method config` for Rails RSpec
92
133
  configuration (Aaron Kromer, #1)
93
134
  - Fix model factory build issue in which registered template attributes, which
94
135
  use symbol keys, are not replaced by custom attributes using string keys
data/Gemfile CHANGED
@@ -16,10 +16,10 @@ end
16
16
 
17
17
  group :debug do
18
18
  gem "pry-byebug", "~> 3.6", require: false
19
- gem "travis", require: false
20
19
  end
21
20
 
22
21
  group :documentation do
22
+ gem 'redcarpet', require: false
23
23
  gem 'yard', '~> 0.9', require: false
24
24
  end
25
25
 
data/README.md CHANGED
@@ -249,7 +249,7 @@ constant so no changes need to be made if that's your preference.
249
249
 
250
250
  Attribute keys may be defined using either strings or symbols. However, they
251
251
  will be stored internally as symbols. This means that when an object instance
252
- is create using the factory the attribute hash will be provided to `new` with
252
+ is created using the factory the attribute hash will be provided to `new` with
253
253
  symbol keys.
254
254
 
255
255
  ##### Dynamic Attribute Values (i.e. Generators)
@@ -418,26 +418,26 @@ There are a few behaviors to note for using the builder:
418
418
 
419
419
  ##### Optional Block
420
420
 
421
- Both `build` and `create` support providing an optional block. This block is
421
+ Both `build` and `build!` support providing an optional block. This block is
422
422
  passed directly to `new` when creating the object. This is to support the
423
423
  common Ruby idiom of yielding `self` within initialize:
424
424
 
425
- ```ruby
426
- class AnyClass
427
- def initialize(attrs = {})
428
- # setup attrs
429
- yield self if block_given?
430
- end
425
+ ```ruby
426
+ class AnyClass
427
+ def initialize(attrs = {})
428
+ # setup attrs
429
+ yield self if block_given?
431
430
  end
431
+ end
432
432
 
433
- RSpec.describe AnyClass, :model_factory do
434
- it "passes the block to the object initializer" do
435
- block_capture = nil
436
- an_object = build("AnyClass") { |instance| block_capture = instance }
437
- expect(block_capture).to be an_object
438
- end
433
+ RSpec.describe AnyClass, :model_factory do
434
+ it "passes the block to the object initializer" do
435
+ block_capture = nil
436
+ an_object = build("AnyClass") { |instance| block_capture = instance }
437
+ expect(block_capture).to be an_object
439
438
  end
440
- ```
439
+ end
440
+ ```
441
441
 
442
442
  Since Ruby always supports passing a block to a method, even if the method does
443
443
  not use the block, it's possible the block will not run if the class being
@@ -451,29 +451,328 @@ this feature.
451
451
 
452
452
  We suggest that you create instances using the following syntax:
453
453
 
454
- ```ruby
455
- created_instance = build("AnyClass").tap(&:save!)
456
- ```
454
+ ```ruby
455
+ let(:an_instance) { build("AnyClass") }
457
456
 
458
- Or alternatively:
457
+ before do
458
+ an_instance.save!
459
+ end
460
+ ```
459
461
 
460
- ```ruby
461
- let(:an_instance) { build("AnyClass") }
462
+ Or alternatively:
462
463
 
463
- before do
464
- an_instance.save!
465
- end
466
- ```
464
+ ```ruby
465
+ created_instance = build("AnyClass")
466
+ created_instance.save!
467
+ ```
467
468
 
468
469
  This way it is explicit what objects need to be persisted and in what order.
469
470
 
470
- However, many of our existing projects use a legacy `create` helper. This is
471
- simply a wrapper around `build.tap(&:save!)`, but it supports omitting the
472
- `save!` call for objects which do not support it.
471
+ This can get tedious at times, especially for those who only need to create an
472
+ object to embed as an attribute of another object:
473
+
474
+ ```ruby
475
+ collaborator = build("AnotherClass")
476
+ collaborator.save!
477
+
478
+ # collaborator is not used against directly after this line
479
+ created_instance = build("AnyClass", collaborator: collaborator)
480
+ created_instance.save!
481
+ ```
482
+
483
+ For these cases the `build!` helper is available. This is simply an alias for
484
+ `build.tap(&:save!)`, but it supports omitting the `save!` call for objects
485
+ which do not support it. While it provides a safety guarantee that `save!` will
486
+ be called (instead of potentially `save`) it is less explicit.
487
+
488
+ ```ruby
489
+ created_instance = build("AnyClass", collaborator: build!("AnotherClass"))
490
+ created_instance.save!
491
+ ```
492
+
493
+ We still discourage the use of `build!` directly in `let` blocks for all of the
494
+ above mentioned reasons.
495
+
496
+ ##### Legacy "Creating" Instances
497
+
498
+ Many of our existing projects use a legacy `create` helper. This is simply an
499
+ alias for `build!`. It is provided only for backwards compatibility support and
500
+ will be removed in a future release. New code should not use this method.
501
+
502
+ ```ruby
503
+ created_instance = create("AnyClass")
504
+ ```
505
+
506
+ ### Negated Matchers
507
+
508
+ This gem defines the following [negated matchers](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/define-negated-matcher)
509
+ to allow for use [composing matchers](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/composing-matchers)
510
+ and with [compound expectations](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/compound-expectations).
511
+
512
+ | Matcher | Inverse Of |
513
+ |-----------------------|------------|
514
+ | `exclude` | [`include`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/include-matcher) |
515
+ | `excluding` | [`including`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/include-matcher) |
516
+ | `not_eq` | [`eq`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/equality-matchers#compare-using-eq-(==)) |
517
+ | `not_change` | [`change`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/change-matcher) |
518
+ | `not_raise_error` | [`raise_error`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/raise-error-matcher) |
519
+ | `not_raise_exception` | [`raise_exception`](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/built-in-matchers/raise-error-matcher) |
520
+
521
+ #### Composing Matchers
522
+
523
+ There is no equivalent of `not_to` for composed matchers when only a subset of
524
+ the values needs to be negated. The negated matchers allow this type of fine
525
+ grain comparison:
526
+
527
+ ```ruby
528
+ x = [1, 2, :value]
529
+ expect(x).to contain_exactly(be_odd, be_even, not_eq(:target))
530
+ ```
531
+
532
+ This also works for verifying / stubbing a message with argument constraints:
533
+
534
+ ```ruby
535
+ allow(obj).to receive(:meth).with(1, 2, not_eq(5))
536
+ obj.meth(1, 2, 3)
537
+ expect(obj).to have_received(:meth).with(not_eq(2), 2, 3)
538
+ ```
539
+
540
+ This is great for verifying option hashes:
541
+
542
+ ```ruby
543
+ expect(obj).to have_received(:meth).with(
544
+ some_value,
545
+ excluding(:some_opt, :another_opt),
546
+ )
547
+ ```
548
+
549
+ #### Compound Negated Matchers
550
+
551
+ Normally it's not possible to chain to a negative match:
552
+
553
+ ```ruby
554
+ a = b = 0
555
+ expect {
556
+ a = 1
557
+ }.not_to change {
558
+ b
559
+ }.from(0).and change {
560
+ a
561
+ }.to(1)
562
+ ```
563
+
564
+ Fails with:
565
+
566
+ NotImplementedError:
567
+ `expect(...).not_to matcher.and matcher` is not supported, since it creates
568
+ a bit of an ambiguity. Instead, define negated versions of whatever
569
+ matchers you wish to negate with `RSpec::Matchers.define_negated_matcher`
570
+ and use `expect(...).to matcher.and matcher`.
571
+
572
+ Per the error the negated matcher allows for the following:
573
+
574
+ ```ruby
575
+ a = b = 0
576
+ expect {
577
+ a = 1
578
+ }.to change {
579
+ a
580
+ }.to(1).and not_change {
581
+ b
582
+ }.from(0)
583
+ ```
584
+
585
+ Similarly, complex expectations can be set on lists:
586
+
587
+ ```ruby
588
+ a = %i[red blue green]
589
+ expect(a).to include(:red).and exclude(:yellow)
590
+ expect(a).to exclude(:yellow).and include(:red)
591
+ ```
592
+
593
+ ### Working with Temp Files
594
+
595
+ These helpers are meant to ease the creation of temporary files to either stub
596
+ the data out or provide a location for data to be saved then verified.
597
+
598
+ In the case of file stubs, using these helpers allows you to co-locate the file
599
+ data with the specs. This makes it easy for someone to read the spec and
600
+ understand the test case; instead of having to find a fixture file and look at
601
+ its data. This also makes it easy to change the data between specs, allowing
602
+ them to focus on just what they need.
603
+
604
+ #### Usage
605
+
606
+ There are multiple ways you can use these helpers. Which method you choose
607
+ depends on how much perceived magic/syntactic sugar you want:
608
+
609
+ - Call the helpers directly on the module:
610
+
611
+ ```ruby
612
+ require 'radius/spec/tempfile'
613
+
614
+ def write_hello_world(filepath)
615
+ File.write filepath, "Hello World"
616
+ end
617
+
618
+ Radius::Spec::Tempfile.using_tempfile do |pathname|
619
+ write_hello_world pathname
620
+ File.read(pathname)
621
+ # => "Hello World"
622
+ end
623
+ ```
624
+ - Include the helper methods explicitly:
625
+
626
+ ```ruby
627
+ require 'radius/spec/tempfile'
628
+
629
+ RSpec.describe AnyClass do
630
+ include Radius::Spec::Tempfile
631
+
632
+ it "includes the file helpers" do
633
+ using_tempfile do |pathname|
634
+ code_under_test pathname
635
+ expect(pathname.read).to eq "Any written data"
636
+ end
637
+ end
638
+ end
639
+ ```
640
+ - Include the helper methods via metadata:
641
+
642
+ ```ruby
643
+ RSpec.describe AnyClass do
644
+ it "includes the file helpers", :tempfile do
645
+ using_tempfile do |pathname|
646
+ code_under_test pathname
647
+ expect(pathname.read).to eq "Any written data"
648
+ end
649
+ end
650
+ end
651
+ ```
652
+
653
+ When using this metadata option you do not need to explicitly require the
654
+ tempfile feature. This gem registers metadata with the RSpec configuration
655
+ when it loads and `RSpec` is defined. When the metadata is first used it
656
+ will automatically require the tempfile feature and include the helpers.
657
+
658
+ Any of following metadata will include the factory helpers:
659
+
660
+ - `:tempfile`
661
+ - `:tmpfile`
662
+
663
+ There are a few additional behaviors to note:
664
+
665
+ - Data can be stubbed by the helper through the `data` keyword arg:
666
+
667
+ ```ruby
668
+ stub_data = "Any file stub data text."
669
+ Radius::Spec::Tempfile.using_tempfile(data: stub_data) do |stubpath|
670
+ File.read(stubpath)
671
+ # => "Any file stub data text."
672
+ end
673
+ ```
674
+
675
+ It can even be inlined using heredocs:
676
+
677
+ ```ruby
678
+ Radius::Spec::Tempfile.using_tempfile(data: <<~TEXT) do |stubpath|
679
+ Any file stub data text.
680
+ TEXT
681
+ # Yard formats heredoc args oddly
682
+ File.read(stubpath)
683
+ # => "Any file stub data text.\n"
684
+ end
685
+ ```
686
+
687
+ > NOTE: That when inlining like this heredocs add an extra new line. To
688
+ > remove it use `.chomp` on the kwarg:
689
+ >
690
+ > ```ruby
691
+ > using_tempfile(data: <<~TEXT.chomp) do |pathname|
692
+ > This has no newline.
693
+ > TEXT
694
+ > # ...
695
+ > end
696
+ > ```
697
+
698
+ - Additional arguments and options are forwarded directly to
699
+ [Tempfile.create](https://ruby-doc.org/stdlib/libdoc/tempfile/rdoc/Tempfile.html#method-c-create)
700
+
701
+ This allows you to set custom file extensions:
702
+
703
+ ```ruby
704
+ Radius::Spec::Tempfile.using_tempfile(%w[custom_name .myext]) do |pathname|
705
+ pathname.extname
706
+ # => ".myext"
707
+ end
708
+ ```
709
+
710
+ Or change the file encoding:
711
+
712
+ ```ruby
713
+ Radius::Spec::Tempfile.using_tempfile(encoding: "ISO-8859-1", data: <<~DATA) do |pathname|
714
+ Résumé
715
+ DATA
716
+ # Yard formats heredoc args oddly
717
+ File.read(pathname)
718
+ # => "R\xE9sum\xE9\n"
719
+ end
720
+ ```
721
+
722
+ ### Common VCR Configuration
723
+
724
+ A project must include both [`vcr`](https://rubygems.org/gems/vcr) and
725
+ [`webmock`](https://rubygems.org/gems/webmock) to use this configuration.
726
+ Neither of those gems will be installed as dependencies of this gem. This is
727
+ intended to give projects more flexibility in choosing which additional features
728
+ they will use.
729
+
730
+ The main `radius/spec/rspec` setup will load the common VCR configuration
731
+ automatically when a spec is tagged with the `:vcr` metadata. This will
732
+ configure VCR to:
733
+
734
+ - save specs to `/spec/cassettes`
735
+
736
+ - use record mode `once` when a single spec or spec file is run
737
+
738
+ This helps ease the development of new specs without requiring any
739
+ configuration / setting changes.
740
+
741
+ - uses record mode `none` otherwise, along setting VCR to fail when unused
742
+ interactions remain in a cassette
743
+
744
+ This is intended to better alert developers to unexpected side effects of
745
+ changes as any addition or removal of a request will cause a failure.
746
+
747
+ - all `Authorization` HTTP headers are filtered by default
748
+
749
+ This is a common oversight when recording specs. Often token based
750
+ authentication is picked up by the other filtered environment settings, but
751
+ basic authentication is not. Additionally, certain types of digest
752
+ authentication may cause specs to leak state. This filtering guards all of
753
+ these cases from accidental credential leak.
754
+
755
+ - the following common sensitive, or often environment variable, settings are
756
+ filtered
757
+
758
+ Those settings which often change between developer machines, and even the
759
+ CI server, can cause for flaky specs. It may also be frustrating for
760
+ developers to have to adjust their local systems to match others just to
761
+ get a few specs to pass. This is intended to help mitigate those issues:
762
+
763
+ - `AWS_ACCESS_KEY_ID`
764
+ - `AWS_SECRET_ACCESS_KEY`
765
+ - `GOOGLE_CLIENT_ID`
766
+ - `GOOGLE_CLIENT_SECRET`
767
+ - `RADIUS_OAUTH_PROVIDER_APP_ID`
768
+ - `RADIUS_OAUTH_PROVIDER_APP_SECRET`
769
+ - `RADIUS_OAUTH_PROVIDER_URL`
770
+
771
+ - a project's local `support/vcr.rb` file will be loaded after the common
772
+ VCR configuration loads; if it's available
473
773
 
474
- ```ruby
475
- created_instance = create("AnyClass")
476
- ```
774
+ This allows projects to overwrite common settings if they need to, as well,
775
+ as add on addition settings or filtering of data.
477
776
 
478
777
  ## Development
479
778
 
@@ -38,6 +38,7 @@ def as_boolean(val, default: nil)
38
38
  false
39
39
  else
40
40
  raise "Unknown boolean value #{val}" if default.nil?
41
+
41
42
  default
42
43
  end
43
44
  end
data/common_rubocop.yml CHANGED
@@ -106,6 +106,10 @@ Metrics/BlockLength:
106
106
  - 'spec/spec_helper.rb'
107
107
  - 'spec/**/*_spec.rb'
108
108
  - 'spec/support/model_factories.rb'
109
+ ExcludedMethods:
110
+ - 'refine'
111
+ - 'RSpec.configure'
112
+ - 'VCR.configure'
109
113
 
110
114
  # We generally prefer to use the default line length of 80. Though sometimes
111
115
  # we just need a little extra space because it makes it easier to read.
@@ -266,7 +270,9 @@ Style/BlockDelimiters:
266
270
  - create!
267
271
  - build
268
272
  - build!
273
+ - default_scope
269
274
  - each_with_object
275
+ - filter_sensitive_data
270
276
  - find
271
277
  - git_source
272
278
  - let
@@ -280,6 +286,19 @@ Style/BlockDelimiters:
280
286
  - proc
281
287
  - it
282
288
 
289
+ # Prefer `Time` over `DateTime`.
290
+ #
291
+ # While these are not necessarily interchangeable we prefer `Time`. According
292
+ # to the Ruby docs `DateTime` is meant more for historical dates; it also does
293
+ # not consider leap seconds or summer time rules.
294
+ #
295
+ # Lastly, `DateTime` is part of the stdlib which is written in Ruby; where as
296
+ # `Time` is part of core and written in C.
297
+ #
298
+ # Configuration parameters: AllowCoercion
299
+ Style/DateTime:
300
+ Enabled: true
301
+
283
302
  # The double negation idiom is a common Ruby-ism. All languages have various
284
303
  # idioms and part of learning the language is learning the common idioms. Once
285
304
  # learning the meaning it is not cryptic as Rubocop implies.
@@ -391,9 +410,10 @@ Style/MethodCalledOnDoEndBlock:
391
410
  Style/MultilineBlockChain:
392
411
  Enabled: false
393
412
 
394
- # Context for this cop is too dependent. Often using the numeric comparison is
395
- # faster. An in certain contexts, Also, depending on the context a numeric
396
- # comparison is more consistent and can even be more natural:
413
+ # Context for this cop is too dependent.
414
+ #
415
+ # Often using the numeric comparison is faster. Also, depending on the context
416
+ # a numeric comparison may produce a more consistent style:
397
417
  #
398
418
  # # numeric comparison is more natural and consistent
399
419
  # if n < 0
@@ -106,6 +106,34 @@ Rails/ApplicationRecord:
106
106
  Rails/CreateTableWithTimestamps:
107
107
  Enabled: false
108
108
 
109
+ # Usage of `find_by` is more expressive of intent than `where.first`. We should
110
+ # check all app code, not just the models to improve intent expression.
111
+ #
112
+ # Since rake tasks often live in `lib` we also check all of lib as well.
113
+ #
114
+ # Configuration parameters: Include.
115
+ # Include: app/models/**/*.rb
116
+ Rails/FindBy:
117
+ Enabled: true
118
+ Include:
119
+ - 'app/**/*.rb'
120
+ - 'lib/**/*.rb'
121
+
122
+ # Usage of `each` for large datasets can be a performance issue; specially a
123
+ # drain on system memory. When possible it's better to use `find_each` so that
124
+ # chunks of data are evaluated at a time.
125
+ #
126
+ # We should check all app code, not just the models to help prevent this. Since
127
+ # rake tasks often live in `lib` we also check all of lib as well.
128
+ #
129
+ # Configuration parameters: Include.
130
+ # Include: app/models/**/*.rb
131
+ Rails/FindEach:
132
+ Enabled: true
133
+ Include:
134
+ - 'app/**/*.rb'
135
+ - 'lib/**/*.rb'
136
+
109
137
  # We understand the trade-offs for using the through model versus a lookup
110
138
  # table. As such this cop is just noise as it flags only those cases we really
111
139
  # do want a lookup table.
@@ -122,7 +150,7 @@ Rails/HasAndBelongsToMany:
122
150
  # For most of us `unless blank?` reads just as easily as `if present?`.
123
151
  # Sometimes contextually, it can read better depending on the branch logic and
124
152
  # surrounding context. As `if present?` requires an additional negation and
125
- # method call it is technically slower. In the general case the perf different
153
+ # method call it is technically slower. In the general case the perf difference
126
154
  # isn't much but in some cases it matters. Thus, we are not enforcing changing
127
155
  # `unless blank?` to `if present?` and are leaving it up to the context to
128
156
  # decide which is a better fit.
@@ -144,6 +172,37 @@ Rails/Present:
144
172
  Rails/ReadWriteAttribute:
145
173
  Enabled: false
146
174
 
175
+ # This ensures we do not ignore potential validation issues in the code. Doing
176
+ # so can lead to strange and surprising bugs where records are expected to
177
+ # be created, or be modified, but are not.
178
+ #
179
+ # # If author is a new record the book may not be created since the FK is
180
+ # # invalid. Perhaps omitting other fields, maybe new required fields, is
181
+ # # an oversight in the book creation as well.
182
+ # author.save
183
+ # Book.create(author: author)
184
+ #
185
+ # Or side effects are expected to occur but they do not:
186
+ #
187
+ # # This is a condensed default Rails scaffold controller for `destroy`.
188
+ # #
189
+ # # Once a `has_many` or `has_one` associations is added which specifies
190
+ # # `dependent: :restrict_with_error` this no longer behaves as expected.
191
+ # # Given such associations are often added much later in time errors in
192
+ # # this action are an all to common oversight in Rails.
193
+ # def destroy
194
+ # @book.destroy
195
+ # respond_to do |format|
196
+ # format.html do
197
+ # redirect_to books_url, notice: 'Book was successfully destroyed.'
198
+ # end
199
+ # end
200
+ # end
201
+ #
202
+ # Configuration parameters: AllowImplicitReturn, AllowedReceivers.
203
+ Rails/SaveBang:
204
+ Enabled: true
205
+
147
206
  # According to the Rails docs while the following methods skip validations they
148
207
  # only update the specified (single) attribute reducing risks. We'd rather not
149
208
  # warn for those cases:
@@ -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,44 +427,56 @@ 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
@@ -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
@@ -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,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
@@ -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,100 @@
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
+ # rubocop:disable Lint/HandleExceptions
95
+ begin
96
+ require 'support/vcr'
97
+ rescue LoadError
98
+ # Ignore as this is an optional convenience feature
99
+ end
100
+ # rubocop:enable Lint/HandleExceptions
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Radius
4
4
  module Spec
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
data/radius-spec.gemspec CHANGED
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.required_ruby_version = ">= 2.5"
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.59.1"
35
35
 
36
36
  spec.add_development_dependency "bundler", "~> 1.16"
37
37
  spec.add_development_dependency "rake", "~> 12.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radius-spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radius Networks
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2018-07-10 00:00:00.000000000 Z
12
+ date: 2018-09-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: 0.58.1
34
+ version: 0.59.1
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 0.58.1
41
+ version: 0.59.1
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -109,7 +109,6 @@ files:
109
109
  - bin/rspec
110
110
  - bin/rubocop
111
111
  - bin/setup
112
- - bin/travis
113
112
  - bin/yard
114
113
  - common_rubocop.yml
115
114
  - common_rubocop_rails.yml
@@ -117,6 +116,9 @@ files:
117
116
  - lib/radius/spec/model_factory.rb
118
117
  - lib/radius/spec/rails.rb
119
118
  - lib/radius/spec/rspec.rb
119
+ - lib/radius/spec/rspec/negated_matchers.rb
120
+ - lib/radius/spec/tempfile.rb
121
+ - lib/radius/spec/vcr.rb
120
122
  - lib/radius/spec/version.rb
121
123
  - radius-spec.gemspec
122
124
  homepage: https://github.com/RadiusNetworks/radius-spec
@@ -124,8 +126,8 @@ licenses:
124
126
  - Apache-2.0
125
127
  metadata:
126
128
  bug_tracker_uri: https://github.com/RadiusNetworks/radius-spec/issues
127
- changelog_uri: https://github.com/RadiusNetworks/radius-spec/blob/v0.4.0/CHANGELOG.md
128
- source_code_uri: https://github.com/RadiusNetworks/radius-spec/tree/v0.4.0
129
+ changelog_uri: https://github.com/RadiusNetworks/radius-spec/blob/v0.5.0/CHANGELOG.md
130
+ source_code_uri: https://github.com/RadiusNetworks/radius-spec/tree/v0.5.0
129
131
  post_install_message:
130
132
  rdoc_options: []
131
133
  require_paths:
data/bin/travis DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'travis' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- require "pathname"
12
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
- Pathname.new(__FILE__).realpath)
14
-
15
- bundle_binstub = File.expand_path("../bundle", __FILE__)
16
-
17
- if File.file?(bundle_binstub)
18
- if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
19
- load(bundle_binstub)
20
- else
21
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
- end
24
- end
25
-
26
- require "rubygems"
27
- require "bundler/setup"
28
-
29
- load Gem.bin_path("travis", "travis")