technicalpickles-shoulda 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +12 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +132 -0
  4. data/Rakefile +72 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda/action_mailer/assertions.rb +39 -0
  7. data/lib/shoulda/action_mailer.rb +10 -0
  8. data/lib/shoulda/active_record/assertions.rb +84 -0
  9. data/lib/shoulda/active_record/macros.rb +684 -0
  10. data/lib/shoulda/active_record.rb +12 -0
  11. data/lib/shoulda/assertions.rb +45 -0
  12. data/lib/shoulda/context.rb +309 -0
  13. data/lib/shoulda/controller/formats/html.rb +201 -0
  14. data/lib/shoulda/controller/formats/xml.rb +170 -0
  15. data/lib/shoulda/controller/helpers.rb +64 -0
  16. data/lib/shoulda/controller/macros.rb +171 -0
  17. data/lib/shoulda/controller/resource_options.rb +236 -0
  18. data/lib/shoulda/controller/routing/macros.rb +47 -0
  19. data/lib/shoulda/controller/routing.rb +10 -0
  20. data/lib/shoulda/controller.rb +31 -0
  21. data/lib/shoulda/helpers.rb +10 -0
  22. data/lib/shoulda/macros.rb +80 -0
  23. data/lib/shoulda/private_helpers.rb +22 -0
  24. data/lib/shoulda/proc_extensions.rb +14 -0
  25. data/lib/shoulda/rails.rb +19 -0
  26. data/lib/shoulda/tasks/list_tests.rake +24 -0
  27. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  28. data/lib/shoulda/tasks.rb +3 -0
  29. data/lib/shoulda.rb +16 -0
  30. data/test/README +36 -0
  31. data/test/fixtures/addresses.yml +3 -0
  32. data/test/fixtures/friendships.yml +0 -0
  33. data/test/fixtures/posts.yml +5 -0
  34. data/test/fixtures/products.yml +0 -0
  35. data/test/fixtures/taggings.yml +0 -0
  36. data/test/fixtures/tags.yml +9 -0
  37. data/test/fixtures/users.yml +6 -0
  38. data/test/functional/posts_controller_test.rb +72 -0
  39. data/test/functional/users_controller_test.rb +36 -0
  40. data/test/other/context_test.rb +145 -0
  41. data/test/other/convert_to_should_syntax_test.rb +63 -0
  42. data/test/other/helpers_test.rb +183 -0
  43. data/test/other/private_helpers_test.rb +34 -0
  44. data/test/other/should_test.rb +266 -0
  45. data/test/rails_root/app/controllers/application.rb +25 -0
  46. data/test/rails_root/app/controllers/posts_controller.rb +78 -0
  47. data/test/rails_root/app/controllers/users_controller.rb +81 -0
  48. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  49. data/test/rails_root/app/helpers/posts_helper.rb +2 -0
  50. data/test/rails_root/app/helpers/users_helper.rb +2 -0
  51. data/test/rails_root/app/models/address.rb +7 -0
  52. data/test/rails_root/app/models/dog.rb +5 -0
  53. data/test/rails_root/app/models/flea.rb +3 -0
  54. data/test/rails_root/app/models/friendship.rb +4 -0
  55. data/test/rails_root/app/models/post.rb +12 -0
  56. data/test/rails_root/app/models/product.rb +12 -0
  57. data/test/rails_root/app/models/tag.rb +8 -0
  58. data/test/rails_root/app/models/tagging.rb +4 -0
  59. data/test/rails_root/app/models/user.rb +28 -0
  60. data/test/rails_root/app/views/layouts/posts.rhtml +17 -0
  61. data/test/rails_root/app/views/layouts/users.rhtml +17 -0
  62. data/test/rails_root/app/views/posts/edit.rhtml +27 -0
  63. data/test/rails_root/app/views/posts/index.rhtml +25 -0
  64. data/test/rails_root/app/views/posts/new.rhtml +26 -0
  65. data/test/rails_root/app/views/posts/show.rhtml +18 -0
  66. data/test/rails_root/app/views/users/edit.rhtml +22 -0
  67. data/test/rails_root/app/views/users/index.rhtml +22 -0
  68. data/test/rails_root/app/views/users/new.rhtml +21 -0
  69. data/test/rails_root/app/views/users/show.rhtml +13 -0
  70. data/test/rails_root/config/boot.rb +109 -0
  71. data/test/rails_root/config/database.yml +4 -0
  72. data/test/rails_root/config/environment.rb +14 -0
  73. data/test/rails_root/config/environments/sqlite3.rb +0 -0
  74. data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
  75. data/test/rails_root/config/initializers/shoulda.rb +8 -0
  76. data/test/rails_root/config/routes.rb +6 -0
  77. data/test/rails_root/db/migrate/001_create_users.rb +18 -0
  78. data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
  79. data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
  80. data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
  81. data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
  82. data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
  83. data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
  84. data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  85. data/test/rails_root/db/migrate/009_create_products.rb +17 -0
  86. data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
  87. data/test/rails_root/db/schema.rb +0 -0
  88. data/test/rails_root/public/404.html +30 -0
  89. data/test/rails_root/public/422.html +30 -0
  90. data/test/rails_root/public/500.html +30 -0
  91. data/test/rails_root/script/console +3 -0
  92. data/test/rails_root/script/generate +3 -0
  93. data/test/test_helper.rb +31 -0
  94. data/test/unit/address_test.rb +10 -0
  95. data/test/unit/dog_test.rb +7 -0
  96. data/test/unit/flea_test.rb +6 -0
  97. data/test/unit/friendship_test.rb +6 -0
  98. data/test/unit/post_test.rb +15 -0
  99. data/test/unit/product_test.rb +27 -0
  100. data/test/unit/tag_test.rb +10 -0
  101. data/test/unit/tagging_test.rb +6 -0
  102. data/test/unit/user_test.rb +47 -0
  103. metadata +197 -0
@@ -0,0 +1,170 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ module Controller # :nodoc:
4
+ module XML
5
+ def self.included(other) #:nodoc:
6
+ other.class_eval do
7
+ extend ThoughtBot::Shoulda::Controller::XML::ClassMethods
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ # Macro that creates a test asserting that the controller responded with an XML content-type
13
+ # and that the XML contains +<name/>+ as the root element.
14
+ def should_respond_with_xml_for(name = nil)
15
+ should "have ContentType set to 'application/xml'" do
16
+ assert_xml_response
17
+ end
18
+
19
+ if name
20
+ should "return <#{name}/> as the root element" do
21
+ body = @response.body.first(100).map {|l| " #{l}"}
22
+ assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element."
23
+ end
24
+ end
25
+ end
26
+ alias should_respond_with_xml should_respond_with_xml_for
27
+
28
+ protected
29
+
30
+ def make_show_xml_tests(res) # :nodoc:
31
+ context "on GET to #{controller_name_from_class}#show as xml" do
32
+ setup do
33
+ request_xml
34
+ record = get_existing_record(res)
35
+ parent_params = make_parent_params(res, record)
36
+ get :show, parent_params.merge({ res.identifier => record.to_param })
37
+ end
38
+
39
+ if res.denied.actions.include?(:show)
40
+ should_not_assign_to res.object
41
+ should_respond_with 401
42
+ else
43
+ should_assign_to res.object
44
+ should_respond_with :success
45
+ should_respond_with_xml_for res.object
46
+ end
47
+ end
48
+ end
49
+
50
+ def make_edit_xml_tests(res) # :nodoc:
51
+ # XML doesn't need an :edit action
52
+ end
53
+
54
+ def make_new_xml_tests(res) # :nodoc:
55
+ # XML doesn't need a :new action
56
+ end
57
+
58
+ def make_index_xml_tests(res) # :nodoc:
59
+ context "on GET to #{controller_name_from_class}#index as xml" do
60
+ setup do
61
+ request_xml
62
+ parent_params = make_parent_params(res)
63
+ get(:index, parent_params)
64
+ end
65
+
66
+ if res.denied.actions.include?(:index)
67
+ should_not_assign_to res.object.to_s.pluralize
68
+ should_respond_with 401
69
+ else
70
+ should_respond_with :success
71
+ should_respond_with_xml_for res.object.to_s.pluralize
72
+ should_assign_to res.object.to_s.pluralize
73
+ end
74
+ end
75
+ end
76
+
77
+ def make_destroy_xml_tests(res) # :nodoc:
78
+ context "on DELETE to #{controller_name_from_class}#destroy as xml" do
79
+ setup do
80
+ request_xml
81
+ @record = get_existing_record(res)
82
+ parent_params = make_parent_params(res, @record)
83
+ delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
84
+ end
85
+
86
+ if res.denied.actions.include?(:destroy)
87
+ should_respond_with 401
88
+
89
+ should "not destroy record" do
90
+ assert @record.reload
91
+ end
92
+ else
93
+ should "destroy record" do
94
+ assert_raises(::ActiveRecord::RecordNotFound, "@#{res.object} was not destroyed.") do
95
+ @record.reload
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def make_create_xml_tests(res) # :nodoc:
103
+ context "on POST to #{controller_name_from_class}#create as xml" do
104
+ setup do
105
+ request_xml
106
+ parent_params = make_parent_params(res)
107
+ @count = res.klass.count
108
+ post :create, parent_params.merge(res.object => res.create.params)
109
+ end
110
+
111
+ if res.denied.actions.include?(:create)
112
+ should_respond_with 401
113
+ should_not_assign_to res.object
114
+
115
+ should "not create new record" do
116
+ assert_equal @count, res.klass.count
117
+ end
118
+ else
119
+ should_assign_to res.object
120
+
121
+ should "not have errors on @#{res.object}" do
122
+ assert_equal [], pretty_error_messages(assigns(res.object)), "@#{res.object} has errors:"
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def make_update_xml_tests(res) # :nodoc:
129
+ context "on PUT to #{controller_name_from_class}#update as xml" do
130
+ setup do
131
+ request_xml
132
+ @record = get_existing_record(res)
133
+ parent_params = make_parent_params(res, @record)
134
+ put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
135
+ end
136
+
137
+ if res.denied.actions.include?(:update)
138
+ should_not_assign_to res.object
139
+ should_respond_with 401
140
+ else
141
+ should_assign_to res.object
142
+
143
+ should "not have errors on @#{res.object}" do
144
+ assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # Sets the next request's format to 'application/xml'
152
+ def request_xml
153
+ @request.accept = "application/xml"
154
+ end
155
+
156
+ # Asserts that the controller's response was 'application/xml'
157
+ def assert_xml_response
158
+ content_type = (@response.headers["Content-Type"] || @response.headers["type"]).to_s
159
+ regex = %r{\bapplication/xml\b}
160
+
161
+ msg = "Content Type '#{content_type.inspect}' doesn't match '#{regex.inspect}'\n"
162
+ msg += "Body: #{@response.body.first(100).chomp} ..."
163
+
164
+ assert_match regex, content_type, msg
165
+ end
166
+
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,64 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ module Controller # :nodoc:
4
+ module Helpers # :nodoc:
5
+ private # :enddoc:
6
+
7
+ SPECIAL_INSTANCE_VARIABLES = %w{
8
+ _cookies
9
+ _flash
10
+ _headers
11
+ _params
12
+ _request
13
+ _response
14
+ _session
15
+ action_name
16
+ before_filter_chain_aborted
17
+ cookies
18
+ flash
19
+ headers
20
+ ignore_missing_templates
21
+ logger
22
+ params
23
+ request
24
+ request_origin
25
+ response
26
+ session
27
+ template
28
+ template_class
29
+ template_root
30
+ url
31
+ variables_added
32
+ }.map(&:to_s)
33
+
34
+ def instantiate_variables_from_assigns(*names, &blk)
35
+ old = {}
36
+ names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty?
37
+ names.each do |name|
38
+ old[name] = instance_variable_get("@#{name}")
39
+ instance_variable_set("@#{name}", assigns(name.to_sym))
40
+ end
41
+ blk.call
42
+ names.each do |name|
43
+ instance_variable_set("@#{name}", old[name])
44
+ end
45
+ end
46
+
47
+ def get_existing_record(res) # :nodoc:
48
+ returning(instance_variable_get("@#{res.object}")) do |record|
49
+ assert(record, "This test requires you to set @#{res.object} in your setup block")
50
+ end
51
+ end
52
+
53
+ def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc:
54
+ parent_names ||= resource.parents.reverse
55
+ return {} if parent_names == [] # Base case
56
+ parent_name = parent_names.shift
57
+ parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first)
58
+
59
+ { :"#{parent_name}_id" => parent.to_param }.merge(make_parent_params(resource, parent, parent_names))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,171 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ module Controller # :nodoc:
4
+ # = Macro test helpers for your controllers
5
+ #
6
+ # By using the macro helpers you can quickly and easily create concise and easy to read test suites.
7
+ #
8
+ # This code segment:
9
+ # context "on GET to :show for first record" do
10
+ # setup do
11
+ # get :show, :id => 1
12
+ # end
13
+ #
14
+ # should_assign_to :user
15
+ # should_respond_with :success
16
+ # should_render_template :show
17
+ # should_not_set_the_flash
18
+ #
19
+ # should "do something else really cool" do
20
+ # assert_equal 1, assigns(:user).id
21
+ # end
22
+ # end
23
+ #
24
+ # Would produce 5 tests for the +show+ action
25
+ #
26
+ # Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
27
+ # controller responds restfully to a variety of requested formats.
28
+ module Macros
29
+ # :section: should_be_restful
30
+ # Generates a full suite of tests for a restful controller.
31
+ #
32
+ # The following definition will generate tests for the +index+, +show+, +new+,
33
+ # +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats:
34
+ #
35
+ # should_be_restful do |resource|
36
+ # resource.parent = :user
37
+ #
38
+ # resource.create.params = { :title => "first post", :body => 'blah blah blah'}
39
+ # resource.update.params = { :title => "changed" }
40
+ # end
41
+ #
42
+ # This generates about 40 tests, all of the format:
43
+ # "on GET to :show should assign @user."
44
+ # "on GET to :show should not set the flash."
45
+ # "on GET to :show should render 'show' template."
46
+ # "on GET to :show should respond with success."
47
+ # "on GET to :show as xml should assign @user."
48
+ # "on GET to :show as xml should have ContentType set to 'application/xml'."
49
+ # "on GET to :show as xml should respond with success."
50
+ # "on GET to :show as xml should return <user/> as the root element."
51
+ # The +resource+ parameter passed into the block is a ResourceOptions object, and
52
+ # is used to configure the tests for the details of your resources.
53
+ #
54
+ def should_be_restful(&blk) # :yields: resource
55
+ resource = ResourceOptions.new
56
+ blk.call(resource)
57
+ resource.normalize!(self)
58
+
59
+ resource.formats.each do |format|
60
+ resource.actions.each do |action|
61
+ if self.respond_to? :"make_#{action}_#{format}_tests"
62
+ self.send(:"make_#{action}_#{format}_tests", resource)
63
+ else
64
+ should "test #{action} #{format}" do
65
+ flunk "Test for #{action} as #{format} not implemented"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # :section: Test macros
73
+ # Macro that creates a test asserting that the flash contains the given value.
74
+ # val can be a String, a Regex, or nil (indicating that the flash should not be set)
75
+ #
76
+ # Example:
77
+ #
78
+ # should_set_the_flash_to "Thank you for placing this order."
79
+ # should_set_the_flash_to /created/i
80
+ # should_set_the_flash_to nil
81
+ def should_set_the_flash_to(val)
82
+ if val
83
+ should "have #{val.inspect} in the flash" do
84
+ assert_contains flash.values, val, ", Flash: #{flash.inspect}"
85
+ end
86
+ else
87
+ should "not set the flash" do
88
+ assert_equal({}, flash, "Flash was set to:\n#{flash.inspect}")
89
+ end
90
+ end
91
+ end
92
+
93
+ # Macro that creates a test asserting that the flash is empty. Same as
94
+ # @should_set_the_flash_to nil@
95
+ def should_not_set_the_flash
96
+ should_set_the_flash_to nil
97
+ end
98
+
99
+ # Macro that creates a test asserting that the controller assigned to
100
+ # each of the named instance variable(s).
101
+ #
102
+ # Example:
103
+ #
104
+ # should_assign_to :user, :posts
105
+ def should_assign_to(*names)
106
+ names.each do |name|
107
+ should "assign @#{name}" do
108
+ assert assigns(name.to_sym), "The action isn't assigning to @#{name}"
109
+ end
110
+ end
111
+ end
112
+
113
+ # Macro that creates a test asserting that the controller did not assign to
114
+ # any of the named instance variable(s).
115
+ #
116
+ # Example:
117
+ #
118
+ # should_not_assign_to :user, :posts
119
+ def should_not_assign_to(*names)
120
+ names.each do |name|
121
+ should "not assign to @#{name}" do
122
+ assert !assigns(name.to_sym), "@#{name} was visible"
123
+ end
124
+ end
125
+ end
126
+
127
+ # Macro that creates a test asserting that the controller responded with a 'response' status code.
128
+ # Example:
129
+ #
130
+ # should_respond_with :success
131
+ def should_respond_with(response)
132
+ should "respond with #{response}" do
133
+ assert_response response
134
+ end
135
+ end
136
+
137
+ # Macro that creates a test asserting that the controller rendered the given template.
138
+ # Example:
139
+ #
140
+ # should_render_template :new
141
+ def should_render_template(template)
142
+ should "render template #{template.inspect}" do
143
+ assert_template template.to_s
144
+ end
145
+ end
146
+
147
+ # Macro that creates a test asserting that the controller returned a redirect to the given path.
148
+ # The given string is evaled to produce the resulting redirect path. All of the instance variables
149
+ # set by the controller are available to the evaled string.
150
+ # Example:
151
+ #
152
+ # should_redirect_to '"/"'
153
+ # should_redirect_to "users_url(@user)"
154
+ def should_redirect_to(url)
155
+ should "redirect to #{url.inspect}" do
156
+ instantiate_variables_from_assigns do
157
+ assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__)
158
+ end
159
+ end
160
+ end
161
+
162
+ # Macro that creates a test asserting that the rendered view contains a <form> element.
163
+ def should_render_a_form
164
+ should "display a form" do
165
+ assert_select "form", true, "The template doesn't contain a <form> element"
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,236 @@
1
+ module ThoughtBot # :nodoc:
2
+ module Shoulda # :nodoc:
3
+ module Controller
4
+ # Formats tested by #should_be_restful. Defaults to [:html, :xml]
5
+ VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
6
+ VALID_FORMATS.each {|f| require "shoulda/controller/formats/#{f}"}
7
+
8
+ # Actions tested by #should_be_restful
9
+ VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
10
+
11
+ # A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
12
+ #
13
+ # Example:
14
+ # class UsersControllerTest < Test::Unit::TestCase
15
+ # fixtures :all
16
+ #
17
+ # def setup
18
+ # ...normal setup code...
19
+ # @user = User.find(:first)
20
+ # end
21
+ #
22
+ # should_be_restful do |resource|
23
+ # resource.identifier = :id
24
+ # resource.klass = User
25
+ # resource.object = :user
26
+ # resource.parent = []
27
+ # resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
28
+ # resource.formats = [:html, :xml]
29
+ #
30
+ # resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
31
+ # resource.update.params = { :name => "sue" }
32
+ #
33
+ # resource.create.redirect = "user_url(@user)"
34
+ # resource.update.redirect = "user_url(@user)"
35
+ # resource.destroy.redirect = "users_url"
36
+ #
37
+ # resource.create.flash = /created/i
38
+ # resource.update.flash = /updated/i
39
+ # resource.destroy.flash = /removed/i
40
+ # end
41
+ # end
42
+ #
43
+ # Whenever possible, the resource attributes will be set to sensible defaults.
44
+ #
45
+ class ResourceOptions
46
+ # Configuration options for the create, update, destroy actions under should_be_restful
47
+ class ActionOptions
48
+ # String evaled to get the target of the redirection.
49
+ # All of the instance variables set by the controller will be available to the
50
+ # evaled code.
51
+ #
52
+ # Example:
53
+ # resource.create.redirect = "user_url(@user.company, @user)"
54
+ #
55
+ # Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
56
+ attr_accessor :redirect
57
+
58
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
59
+ #
60
+ # Defaults:
61
+ # destroy:: /removed/
62
+ # create:: /created/
63
+ # update:: /updated/
64
+ attr_accessor :flash
65
+
66
+ # Hash describing the params that should be sent in with this action.
67
+ attr_accessor :params
68
+ end
69
+
70
+ # Configuration options for the denied actions under should_be_restful
71
+ #
72
+ # Example:
73
+ # context "The public" do
74
+ # setup do
75
+ # @request.session[:logged_in] = false
76
+ # end
77
+ #
78
+ # should_be_restful do |resource|
79
+ # resource.parent = :user
80
+ #
81
+ # resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
82
+ # resource.denied.flash = /get outta here/i
83
+ # resource.denied.redirect = 'new_session_url'
84
+ # end
85
+ # end
86
+ #
87
+ class DeniedOptions
88
+ # String evaled to get the target of the redirection.
89
+ # All of the instance variables set by the controller will be available to the
90
+ # evaled code.
91
+ #
92
+ # Example:
93
+ # resource.create.redirect = "user_url(@user.company, @user)"
94
+ attr_accessor :redirect
95
+
96
+ # String or Regexp describing a value expected in the flash. Will match against any flash key.
97
+ #
98
+ # Example:
99
+ # resource.create.flash = /created/
100
+ attr_accessor :flash
101
+
102
+ # Actions that should be denied (only used by resource.denied). <i>Note that these actions will
103
+ # only be tested if they are also listed in +resource.actions+</i>
104
+ # The special value of :all will deny all of the REST actions.
105
+ attr_accessor :actions
106
+ end
107
+
108
+ # Name of key in params that references the primary key.
109
+ # Will almost always be :id (default), unless you are using a plugin or have patched rails.
110
+ attr_accessor :identifier
111
+
112
+ # Name of the ActiveRecord class this resource is responsible for. Automatically determined from
113
+ # test class if not explicitly set. UserTest => "User"
114
+ attr_accessor :klass
115
+
116
+ # Name of the instantiated ActiveRecord object that should be used by some of the tests.
117
+ # Defaults to the underscored name of the AR class. CompanyManager => :company_manager
118
+ attr_accessor :object
119
+
120
+ # Name of the parent AR objects. Can be set as parent= or parents=, and can take either
121
+ # the name of the parent resource (if there's only one), or an array of names (if there's
122
+ # more than one).
123
+ #
124
+ # Example:
125
+ # # in the routes...
126
+ # map.resources :companies do
127
+ # map.resources :people do
128
+ # map.resources :limbs
129
+ # end
130
+ # end
131
+ #
132
+ # # in the tests...
133
+ # class PeopleControllerTest < Test::Unit::TestCase
134
+ # should_be_restful do |resource|
135
+ # resource.parent = :companies
136
+ # end
137
+ # end
138
+ #
139
+ # class LimbsControllerTest < Test::Unit::TestCase
140
+ # should_be_restful do |resource|
141
+ # resource.parents = [:companies, :people]
142
+ # end
143
+ # end
144
+ attr_accessor :parent
145
+ alias parents parent
146
+ alias parents= parent=
147
+
148
+ # Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
149
+ # Tests for each actionw will only be generated if the action is listed here.
150
+ # The special value of :all will test all of the REST actions.
151
+ #
152
+ # Example (for a read-only controller):
153
+ # resource.actions = [:show, :index]
154
+ attr_accessor :actions
155
+
156
+ # Formats that should be tested. Must be a subset of VALID_FORMATS (default).
157
+ # Each action will be tested against the formats listed here. The special value
158
+ # of :all will test all of the supported formats.
159
+ #
160
+ # Example:
161
+ # resource.actions = [:html, :xml]
162
+ attr_accessor :formats
163
+
164
+ # ActionOptions object specifying options for the create action.
165
+ attr_accessor :create
166
+
167
+ # ActionOptions object specifying options for the update action.
168
+ attr_accessor :update
169
+
170
+ # ActionOptions object specifying options for the desrtoy action.
171
+ attr_accessor :destroy
172
+
173
+ # DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
174
+ attr_accessor :denied
175
+
176
+ def initialize # :nodoc:
177
+ @create = ActionOptions.new
178
+ @update = ActionOptions.new
179
+ @destroy = ActionOptions.new
180
+ @denied = DeniedOptions.new
181
+
182
+ @create.flash ||= /created/i
183
+ @update.flash ||= /updated/i
184
+ @destroy.flash ||= /removed/i
185
+ @denied.flash ||= /denied/i
186
+
187
+ @create.params ||= {}
188
+ @update.params ||= {}
189
+
190
+ @actions = VALID_ACTIONS
191
+ @formats = VALID_FORMATS
192
+ @denied.actions = []
193
+ end
194
+
195
+ def normalize!(target) # :nodoc:
196
+ @denied.actions = VALID_ACTIONS if @denied.actions == :all
197
+ @actions = VALID_ACTIONS if @actions == :all
198
+ @formats = VALID_FORMATS if @formats == :all
199
+
200
+ @denied.actions = @denied.actions.map(&:to_sym)
201
+ @actions = @actions.map(&:to_sym)
202
+ @formats = @formats.map(&:to_sym)
203
+
204
+ ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
205
+ ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
206
+ ensure_valid_members(@formats, VALID_FORMATS, 'formats')
207
+
208
+ @identifier ||= :id
209
+ @klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
210
+ @object ||= @klass.name.tableize.singularize
211
+ @parent ||= []
212
+ @parent = [@parent] unless @parent.is_a? Array
213
+
214
+ collection_helper = [@parent, @object.to_s.pluralize, 'url'].flatten.join('_')
215
+ collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
216
+ @destroy.redirect ||= "#{collection_helper}(#{collection_args})"
217
+
218
+ member_helper = [@parent, @object, 'url'].flatten.join('_')
219
+ member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
220
+ @create.redirect ||= "#{member_helper}(#{member_args})"
221
+ @update.redirect ||= "#{member_helper}(#{member_args})"
222
+ @denied.redirect ||= "new_session_url"
223
+ end
224
+
225
+ private
226
+
227
+ def ensure_valid_members(ary, valid_members, name) # :nodoc:
228
+ invalid = ary - valid_members
229
+ raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+
@@ -0,0 +1,47 @@
1
+ module ThoughtBot
2
+ module Shoulda
3
+ module Controller
4
+ module Routing
5
+ module Macros
6
+ # Macro that creates a routing test. It tries to use the given HTTP
7
+ # +method+ on the given +path+, and asserts that it routes to the
8
+ # given +options+.
9
+ #
10
+ # If you don't specify a :controller, it will try to guess the controller
11
+ # based on the current test.
12
+ #
13
+ # +to_param+ is called on the +options+ given.
14
+ #
15
+ # Examples:
16
+ #
17
+ # should_route :get, '/posts', :action => :index
18
+ # should_route :post, '/posts', :controller => :posts, :action => :create
19
+ # should_route :get, '/posts/1', :action => :show, :id => 1
20
+ # should_route :put, '/posts/1', :action => :update, :id => "1"
21
+ # should_route :delete, '/posts/1', :action => :destroy, :id => 1
22
+ # should_route :get, '/posts/new', :action => :new
23
+ #
24
+ def should_route(method, path, options)
25
+ unless options[:controller]
26
+ options[:controller] = self.name.gsub(/ControllerTest$/, '').tableize
27
+ end
28
+ options[:controller] = options[:controller].to_s
29
+ options[:action] = options[:action].to_s
30
+
31
+ populated_path = path.dup
32
+ options.each do |key, value|
33
+ options[key] = value.to_param if value.respond_to? :to_param
34
+ populated_path.gsub!(key.inspect, value.to_s)
35
+ end
36
+
37
+ should_name = "route #{method.to_s.upcase} #{populated_path} to/from #{options.inspect}"
38
+
39
+ should should_name do
40
+ assert_routing({:method => method, :path => populated_path}, options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end