Skip to content
This repository
Browse code

Add a StrongParametersMatcher

  • Loading branch information...
commit 7b15429ebeea49d3e6ba8fed4f9656d1bbd56a2c 1 parent f7337c6
authored December 13, 2012 georgebrock committed January 25, 2013
13  Gemfile.lock
@@ -61,7 +61,7 @@ GEM
61 61
     hike (1.2.1)
62 62
     i18n (0.6.1)
63 63
     journey (1.0.4)
64  
-    json (1.7.5)
  64
+    json (1.7.6)
65 65
     mail (2.4.4)
66 66
       i18n (>= 0.4.0)
67 67
       mime-types (~> 1.16)
@@ -70,9 +70,9 @@ GEM
70 70
     mime-types (1.19)
71 71
     mocha (0.12.7)
72 72
       metaclass (~> 0.0.1)
73  
-    multi_json (1.4.0)
  73
+    multi_json (1.5.0)
74 74
     polyglot (0.3.3)
75  
-    rack (1.4.1)
  75
+    rack (1.4.4)
76 76
     rack-cache (1.2)
77 77
       rack (>= 0.4)
78 78
     rack-ssl (1.3.2)
@@ -94,7 +94,7 @@ GEM
94 94
       rake (>= 0.8.7)
95 95
       rdoc (~> 3.4)
96 96
       thor (>= 0.14.6, < 2.0)
97  
-    rake (10.0.2)
  97
+    rake (10.0.3)
98 98
     rdoc (3.12)
99 99
       json (~> 1.4)
100 100
     rspec (2.8.0)
@@ -117,6 +117,10 @@ GEM
117 117
       rack (~> 1.0)
118 118
       tilt (~> 1.1, != 1.3.0)
119 119
     sqlite3 (1.3.6)
  120
+    strong_parameters (0.1.5)
  121
+      actionpack (~> 3.1)
  122
+      activemodel (~> 3.1)
  123
+      railties (~> 3.1)
120 124
     thor (0.16.0)
121 125
     tilt (1.3.3)
122 126
     treetop (1.4.12)
@@ -142,4 +146,5 @@ DEPENDENCIES
142 146
   shoulda-context (~> 1.0.0)
143 147
   shoulda-matchers!
144 148
   sqlite3
  149
+  strong_parameters
145 150
   therubyrhino
2  README.md
Source Rendered
@@ -51,6 +51,8 @@ Matchers to test common patterns:
51 51
 
52 52
 ```ruby
53 53
 describe PostsController, "#show" do
  54
+  it { should permit(:title, :body).for(:create) }
  55
+
54 56
   context "for a fictional user" do
55 57
     before do
56 58
       get :show, :id => 1
15  gemfiles/3.0.gemfile.lock
... ...
@@ -1,5 +1,5 @@
1 1
 PATH
2  
-  remote: /Users/gabe/thoughtbot/open-source/shoulda-matchers
  2
+  remote: /vagrant
3 3
   specs:
4 4
     shoulda-matchers (1.4.2)
5 5
       activesupport (>= 3.0.0)
@@ -56,11 +56,11 @@ GEM
56 56
     diff-lcs (1.1.3)
57 57
     erubis (2.6.6)
58 58
       abstract (>= 1.0.0)
59  
-    ffi (1.2.0)
  59
+    ffi (1.3.1)
60 60
     gherkin (2.11.5)
61 61
       json (>= 1.4.6)
62 62
     i18n (0.5.0)
63  
-    json (1.7.5)
  63
+    json (1.7.6)
64 64
     mail (2.2.19)
65 65
       activesupport (>= 2.3.6)
66 66
       i18n (>= 0.4.0)
@@ -71,7 +71,7 @@ GEM
71 71
     mocha (0.12.7)
72 72
       metaclass (~> 0.0.1)
73 73
     polyglot (0.3.3)
74  
-    rack (1.2.5)
  74
+    rack (1.2.7)
75 75
     rack-mount (0.6.14)
76 76
       rack (>= 1.0.0)
77 77
     rack-test (0.5.7)
@@ -107,7 +107,11 @@ GEM
107 107
       railties (>= 3.0)
108 108
       rspec (~> 2.8.0)
109 109
     shoulda-context (1.0.2)
110  
-    sqlite3 (1.3.6)
  110
+    sqlite3 (1.3.7)
  111
+    strong_parameters (0.1.6)
  112
+      actionpack (~> 3.0)
  113
+      activemodel (~> 3.0)
  114
+      railties (~> 3.0)
111 115
     thor (0.14.6)
112 116
     treetop (1.4.12)
113 117
       polyglot
@@ -132,4 +136,5 @@ DEPENDENCIES
132 136
   shoulda-context (~> 1.0.0)
133 137
   shoulda-matchers!
134 138
   sqlite3
  139
+  strong_parameters
135 140
   therubyrhino
19  gemfiles/3.1.gemfile.lock
... ...
@@ -1,5 +1,5 @@
1 1
 PATH
2  
-  remote: /Users/gabe/thoughtbot/open-source/shoulda-matchers
  2
+  remote: /vagrant
3 3
   specs:
4 4
     shoulda-matchers (1.4.2)
5 5
       activesupport (>= 3.0.0)
@@ -56,15 +56,15 @@ GEM
56 56
       json (>= 1.4.6)
57 57
     diff-lcs (1.1.3)
58 58
     erubis (2.7.0)
59  
-    ffi (1.2.0)
  59
+    ffi (1.3.1)
60 60
     gherkin (2.11.5)
61 61
       json (>= 1.4.6)
62 62
     hike (1.2.1)
63 63
     i18n (0.6.1)
64  
-    jquery-rails (2.1.4)
  64
+    jquery-rails (2.2.0)
65 65
       railties (>= 3.0, < 5.0)
66 66
       thor (>= 0.14, < 2.0)
67  
-    json (1.7.5)
  67
+    json (1.7.6)
68 68
     mail (2.3.3)
69 69
       i18n (>= 0.4.0)
70 70
       mime-types (~> 1.16)
@@ -75,7 +75,7 @@ GEM
75 75
       metaclass (~> 0.0.1)
76 76
     multi_json (1.2.0)
77 77
     polyglot (0.3.3)
78  
-    rack (1.3.6)
  78
+    rack (1.3.9)
79 79
     rack-cache (1.2)
80 80
       rack (>= 0.4)
81 81
     rack-mount (0.8.3)
@@ -115,7 +115,7 @@ GEM
115 115
       activesupport (>= 3.0)
116 116
       railties (>= 3.0)
117 117
       rspec (~> 2.8.0)
118  
-    sass (3.2.3)
  118
+    sass (3.2.5)
119 119
     sass-rails (3.1.6)
120 120
       actionpack (~> 3.1.0)
121 121
       railties (~> 3.1.0)
@@ -126,7 +126,11 @@ GEM
126 126
       hike (~> 1.2)
127 127
       rack (~> 1.0)
128 128
       tilt (~> 1.1, != 1.3.0)
129  
-    sqlite3 (1.3.6)
  129
+    sqlite3 (1.3.7)
  130
+    strong_parameters (0.1.6)
  131
+      actionpack (~> 3.0)
  132
+      activemodel (~> 3.0)
  133
+      railties (~> 3.0)
130 134
     thor (0.14.6)
131 135
     tilt (1.3.3)
132 136
     treetop (1.4.12)
@@ -154,4 +158,5 @@ DEPENDENCIES
154 158
   shoulda-context (~> 1.0.0)
155 159
   shoulda-matchers!
156 160
   sqlite3
  161
+  strong_parameters
157 162
   therubyrhino
21  gemfiles/3.2.gemfile.lock
... ...
@@ -1,5 +1,5 @@
1 1
 PATH
2  
-  remote: /Users/gabe/thoughtbot/open-source/shoulda-matchers
  2
+  remote: /vagrant
3 3
   specs:
4 4
     shoulda-matchers (1.4.2)
5 5
       activesupport (>= 3.0.0)
@@ -55,16 +55,16 @@ GEM
55 55
       json (>= 1.4.6)
56 56
     diff-lcs (1.1.3)
57 57
     erubis (2.7.0)
58  
-    ffi (1.2.0)
  58
+    ffi (1.3.1)
59 59
     gherkin (2.11.5)
60 60
       json (>= 1.4.6)
61 61
     hike (1.2.1)
62 62
     i18n (0.6.1)
63 63
     journey (1.0.4)
64  
-    jquery-rails (2.1.4)
  64
+    jquery-rails (2.2.0)
65 65
       railties (>= 3.0, < 5.0)
66 66
       thor (>= 0.14, < 2.0)
67  
-    json (1.7.5)
  67
+    json (1.7.6)
68 68
     mail (2.4.4)
69 69
       i18n (>= 0.4.0)
70 70
       mime-types (~> 1.16)
@@ -75,7 +75,7 @@ GEM
75 75
       metaclass (~> 0.0.1)
76 76
     multi_json (1.5.0)
77 77
     polyglot (0.3.3)
78  
-    rack (1.4.1)
  78
+    rack (1.4.4)
79 79
     rack-cache (1.2)
80 80
       rack (>= 0.4)
81 81
     rack-ssl (1.3.2)
@@ -113,8 +113,8 @@ GEM
113 113
       activesupport (>= 3.0)
114 114
       railties (>= 3.0)
115 115
       rspec (~> 2.8.0)
116  
-    sass (3.2.3)
117  
-    sass-rails (3.2.5)
  116
+    sass (3.2.5)
  117
+    sass-rails (3.2.6)
118 118
       railties (~> 3.2.0)
119 119
       sass (>= 3.1.10)
120 120
       tilt (~> 1.3)
@@ -124,7 +124,11 @@ GEM
124 124
       multi_json (~> 1.0)
125 125
       rack (~> 1.0)
126 126
       tilt (~> 1.1, != 1.3.0)
127  
-    sqlite3 (1.3.6)
  127
+    sqlite3 (1.3.7)
  128
+    strong_parameters (0.1.6)
  129
+      actionpack (~> 3.0)
  130
+      activemodel (~> 3.0)
  131
+      railties (~> 3.0)
128 132
     thor (0.16.0)
129 133
     tilt (1.3.3)
130 134
     treetop (1.4.12)
@@ -152,4 +156,5 @@ DEPENDENCIES
152 156
   shoulda-context (~> 1.0.0)
153 157
   shoulda-matchers!
154 158
   sqlite3
  159
+  strong_parameters
155 160
   therubyrhino
1  lib/shoulda/matchers/action_controller.rb
@@ -8,6 +8,7 @@
8 8
 require 'shoulda/matchers/action_controller/route_matcher'
9 9
 require 'shoulda/matchers/action_controller/redirect_to_matcher'
10 10
 require 'shoulda/matchers/action_controller/render_template_matcher'
  11
+require 'shoulda/matchers/action_controller/strong_parameters_matcher'
11 12
 
12 13
 module Shoulda
13 14
   module Matchers
118  lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb
... ...
@@ -0,0 +1,118 @@
  1
+require 'bourne'
  2
+begin
  3
+  require 'strong_parameters'
  4
+rescue LoadError
  5
+end
  6
+
  7
+module Shoulda
  8
+  module Matchers
  9
+    module ActionController
  10
+      def permit(*attributes)
  11
+        attributes_and_context = attributes + [self]
  12
+        StrongParametersMatcher.new(*attributes_and_context)
  13
+      end
  14
+
  15
+      class StrongParametersMatcher
  16
+        def initialize(*attributes_and_context)
  17
+          @attributes = attributes_and_context[0...-1]
  18
+          @context = attributes_and_context.last
  19
+          @permitted_params = []
  20
+        end
  21
+
  22
+        def for(action, options = {})
  23
+          @action = action
  24
+          @verb = options[:verb] || verb_for_action
  25
+          self
  26
+        end
  27
+
  28
+        def in_context(context)
  29
+          @context = context
  30
+          self
  31
+        end
  32
+
  33
+        def matches?(controller = nil)
  34
+          simulate_controller_action && parameters_difference.empty?
  35
+        end
  36
+
  37
+        def does_not_match?(controller = nil)
  38
+          simulate_controller_action && parameters_difference.present?
  39
+        end
  40
+
  41
+        def failure_message
  42
+          "Expected controller to permit #{parameters_difference.to_sentence}, but it did not."
  43
+        end
  44
+
  45
+        def negative_failure_message
  46
+          "Expected controller not to permit #{parameters_difference.to_sentence}, but it did."
  47
+        end
  48
+
  49
+        private
  50
+        attr_reader :verb, :action, :attributes, :context
  51
+        attr_accessor :permitted_params
  52
+
  53
+        def simulate_controller_action
  54
+          ensure_action_and_verb_present!
  55
+          model_attrs = stubbed_model_attributes
  56
+
  57
+          context.send(verb, action)
  58
+
  59
+          verify_permit_call(model_attrs)
  60
+        end
  61
+
  62
+        def verify_permit_call(model_attrs)
  63
+          model_attrs.should have_received(:permit).with { |*params|
  64
+            self.permitted_params = params
  65
+          }
  66
+          true
  67
+        rescue RSpec::Expectations::ExpectationNotMetError, Mocha::ExpectationError
  68
+          false
  69
+        end
  70
+
  71
+        def parameters_difference
  72
+          attributes - permitted_params
  73
+        end
  74
+
  75
+        def stubbed_model_attributes
  76
+          extend Mocha::API
  77
+
  78
+          model_attrs = ::ActionController::Parameters.new(arbitrary_attributes)
  79
+          model_attrs.stubs(:permit)
  80
+          ::ActionController::Parameters.any_instance.stubs(:[]).returns(model_attrs)
  81
+
  82
+          model_attrs
  83
+        end
  84
+
  85
+        def ensure_action_and_verb_present!
  86
+          if action.blank?
  87
+            raise ActionNotDefinedError
  88
+          end
  89
+          if verb.blank?
  90
+            raise VerbNotDefinedError
  91
+          end
  92
+        end
  93
+
  94
+        def arbitrary_attributes
  95
+          {:any_key => 'any_value'}
  96
+        end
  97
+
  98
+        def verb_for_action
  99
+          verb_lookup = { :create => :post, :update => :put }
  100
+          verb_lookup[action]
  101
+        end
  102
+      end
  103
+
  104
+      class StrongParametersMatcher::ActionNotDefinedError < StandardError
  105
+        def message
  106
+          'You must specify the controller action using the #for method.'
  107
+        end
  108
+      end
  109
+
  110
+      class StrongParametersMatcher::VerbNotDefinedError < StandardError
  111
+        def message
  112
+          'You must specify an HTTP verb when using a non-RESTful action.' +
  113
+          ' e.g. for(:authorize, :verb => :post)'
  114
+        end
  115
+      end
  116
+    end
  117
+  end
  118
+end
1  shoulda-matchers.gemspec
@@ -27,4 +27,5 @@ Gem::Specification.new do |s|
27 27
   s.add_development_dependency('rails',       '~> 3.0')
28 28
   s.add_development_dependency('rake',        '>= 0.9.2')
29 29
   s.add_development_dependency('rspec-rails', '~> 2.8.1')
  30
+  s.add_development_dependency('strong_parameters')
30 31
 end
142  spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb
... ...
@@ -0,0 +1,142 @@
  1
+require 'spec_helper'
  2
+
  3
+describe Shoulda::Matchers::ActionController do
  4
+  describe ".permit" do
  5
+    it "is true when the sent parameter is allowed" do
  6
+      controller_class = controller_for_resource_with_strong_parameters do
  7
+        params.require(:user).permit(:name)
  8
+      end
  9
+
  10
+      controller_class.should permit(:name).for(:create)
  11
+    end
  12
+
  13
+    it "is false when the sent parameter is not allowed" do
  14
+      controller_class = controller_for_resource_with_strong_parameters do
  15
+        params.require(:user).permit(:name)
  16
+      end
  17
+
  18
+      controller_class.should_not permit(:admin).for(:create)
  19
+    end
  20
+
  21
+    it "allows multiple attributes" do
  22
+      controller_class = controller_for_resource_with_strong_parameters do
  23
+        params.require(:user).permit(:name, :age)
  24
+      end
  25
+
  26
+      controller_class.should permit(:name, :age).for(:create)
  27
+    end
  28
+  end
  29
+end
  30
+
  31
+describe Shoulda::Matchers::ActionController::StrongParametersMatcher do
  32
+  before do
  33
+    controller_for_resource_with_strong_parameters do
  34
+      params.require(:user).permit(:name, :age)
  35
+    end
  36
+  end
  37
+
  38
+  describe "#matches?" do
  39
+    it "is true for a subset of the allowable attributes" do
  40
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, self).for(:create)
  41
+      matcher.matches?.should be_true
  42
+    end
  43
+
  44
+    it "is true for all the allowable attributes" do
  45
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :age, self).for(:create)
  46
+      matcher.matches?.should be_true
  47
+    end
  48
+
  49
+    it "is false when any attributes are not allowed" do
  50
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :admin, self).for(:create)
  51
+      matcher.matches?.should be_false
  52
+    end
  53
+
  54
+    it "is false when permit is not called" do
  55
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, self).for(:new, :verb => :get)
  56
+      matcher.matches?.should be_false
  57
+    end
  58
+
  59
+    it "requires an action" do
  60
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, self)
  61
+      expect{ matcher.matches? }.to raise_error(Shoulda::Matchers::ActionController::StrongParametersMatcher::ActionNotDefinedError)
  62
+    end
  63
+
  64
+    it "requires a verb for non-restful action" do
  65
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, self).for(:authorize)
  66
+      expect{ matcher.matches? }.to raise_error(Shoulda::Matchers::ActionController::StrongParametersMatcher::VerbNotDefinedError)
  67
+    end
  68
+  end
  69
+
  70
+  describe "#does_not_match?" do
  71
+    it "it is true if any of the given attributes are allowed" do
  72
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :admin, self).for(:create)
  73
+      matcher.does_not_match?.should be_true
  74
+    end
  75
+
  76
+    it "it is false if all of the given attribtues are allowed" do
  77
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :age, self).for(:create)
  78
+      matcher.does_not_match?.should be_false
  79
+    end
  80
+  end
  81
+
  82
+  describe "#failure_message" do
  83
+    it "includes all missing attributes" do
  84
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :age, :city, :country, self).for(:create)
  85
+      matcher.matches?
  86
+
  87
+      matcher.failure_message.should eq("Expected controller to permit city and country, but it did not.")
  88
+    end
  89
+  end
  90
+
  91
+  describe "#negative_failure_message" do
  92
+    it "includes all attributes that should not have been allowed but were" do
  93
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, :age, :city, :country, self).for(:create)
  94
+      matcher.does_not_match?.should be_true
  95
+
  96
+      matcher.negative_failure_message.should eq("Expected controller not to permit city and country, but it did.")
  97
+    end
  98
+  end
  99
+
  100
+  describe "#for" do
  101
+    context "when given :create" do
  102
+      it "posts to the controller" do
  103
+        context = stub('context', :post => nil)
  104
+        matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, context).for(:create)
  105
+
  106
+        matcher.matches?
  107
+        context.should have_received(:post).with(:create)
  108
+      end
  109
+    end
  110
+
  111
+    context "when given :update" do
  112
+      it "puts to the controller" do
  113
+        context = stub('context', :put => nil)
  114
+        matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, context).for(:update)
  115
+
  116
+        matcher.matches?
  117
+        context.should have_received(:put).with(:update)
  118
+      end
  119
+    end
  120
+
  121
+    context "when given a custom action and verb" do
  122
+      it "puts to the controller" do
  123
+        context = stub('context', :delete => nil)
  124
+        matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, context).for(:hide, :verb => :delete)
  125
+
  126
+        matcher.matches?
  127
+        context.should have_received(:delete).with(:hide)
  128
+      end
  129
+    end
  130
+  end
  131
+
  132
+  describe "#in_context" do
  133
+    it 'sets the object the controller action is sent to' do
  134
+      context = stub('context', :post => nil)
  135
+      matcher = Shoulda::Matchers::ActionController::StrongParametersMatcher.new(:name, nil).for(:create).in_context(context)
  136
+
  137
+      matcher.matches?
  138
+
  139
+      context.should have_received(:post).with(:create)
  140
+    end
  141
+  end
  142
+end
1  spec/spec_helper.rb
@@ -7,7 +7,6 @@
7 7
 ENV['BUNDLE_GEMFILE'] ||= TESTAPP_ROOT + '/Gemfile'
8 8
 
9 9
 require "#{TESTAPP_ROOT}/config/environment"
10  
-require 'rspec'
11 10
 require 'bourne'
12 11
 require 'shoulda-matchers'
13 12
 require 'rspec/rails'
40  spec/support/controller_builder.rb
@@ -42,18 +42,20 @@ def build_response(opts = {}, &block)
42 42
     create_view("examples/#{action}.html.erb", 'action')
43 43
     create_view("examples/#{partial}.html.erb", 'partial')
44 44
 
  45
+    setup_rails_controller_test(controller_class)
  46
+    get action
  47
+
  48
+    @controller
  49
+  end
  50
+
  51
+  def setup_rails_controller_test(controller_class)
45 52
     @controller = controller_class.new
46  
-    @request    = ActionController::TestRequest.new
47  
-    @response   = ActionController::TestResponse.new
  53
+    @request = ::ActionController::TestRequest.new
  54
+    @response = ::ActionController::TestResponse.new
48 55
 
49 56
     class << self
50 57
       include ActionController::TestCase::Behavior
51 58
     end
52  
-    @routes = Rails.application.routes
53  
-
54  
-    get action
55  
-
56  
-    @controller
57 59
   end
58 60
 
59 61
   def create_view(path, contents)
@@ -62,6 +64,30 @@ def create_view(path, contents)
62 64
     File.open(full_path, 'w') { |file| file.write(contents) }
63 65
   end
64 66
 
  67
+  def controller_for_resource_with_strong_parameters(&block)
  68
+    define_model "User"
  69
+    controller_class = define_controller "Users" do
  70
+      def new
  71
+        @user = User.new
  72
+        render :nothing => true
  73
+      end
  74
+
  75
+      def create
  76
+        @user = User.create(user_params)
  77
+        render :nothing => true
  78
+      end
  79
+
  80
+      private
  81
+      define_method :user_params, &block
  82
+    end
  83
+
  84
+    setup_rails_controller_test(controller_class)
  85
+
  86
+    define_routes { resources :users }
  87
+
  88
+    controller_class
  89
+  end
  90
+
65 91
   private
66 92
 
67 93
   def delete_temporary_views

0 notes on commit 7b15429

Please sign in to comment.
Something went wrong with that request. Please try again.