validates_captcha 0.9.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.
@@ -0,0 +1,108 @@
1
+ require 'active_support/core_ext/string/bytesize'
2
+
3
+ module ValidatesCaptcha
4
+ module Middleware
5
+ # This class acts as the Rack middleware that serves the captcha images.
6
+ #
7
+ # You can implement your own middleware by creating a
8
+ # class that conforms to the method definitions of the example below and
9
+ # assign an instance of it to ValidatesCaptcha#middleware=.
10
+ #
11
+ # Example for a custom middleware:
12
+ #
13
+ # class MyMiddleware
14
+ # def image_path(encrypted_code)
15
+ # '/captchas/image/' + encrypted_code + ValidatesCaptcha.captcha_image_file_extension
16
+ # end
17
+ #
18
+ # def regenerate_path
19
+ # '/captchas/regenerate'
20
+ # end
21
+ #
22
+ # def call(env)
23
+ # if env['PATH_INFO'] =~ /^\/captchas\/image\/([^\.]+)/
24
+ # decrypted_code = ValidatesCaptcha.decrypt_captcha_code($1)
25
+ #
26
+ # if decrypted_code.nil?
27
+ # [422, { 'Content-Type' => 'text/html' }, ['Unprocessable Entity']]
28
+ # else
29
+ # image_data = ValidatesCaptcha.generate_captcha_image(decrypted_code)
30
+ #
31
+ # headers = {
32
+ # 'Content-Length' => image_data.bytesize.to_s,
33
+ # 'Content-Type' => ValidatesCaptcha.captcha_image_mime_type,
34
+ # 'Content-Disposition' => 'inline',
35
+ # 'Content-Transfer-Encoding' => 'binary'
36
+ # }
37
+ #
38
+ # [200, headers, [image_data]]
39
+ # end
40
+ # elsif env['PATH_INFO'] == regenerate_path
41
+ # encrypted_code = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
42
+ # xml = { :encrypted_captcha_code => encrypted_code, :captcha_image_path => image_path(encrypted_code) }.to_xml
43
+ #
44
+ # [200, { 'Content-Type' => 'application/xml' }, [xml]]
45
+ # else
46
+ # [404, { 'Content-Type' => 'text/html' }, ['Not Found']]
47
+ # end
48
+ # end
49
+ # end
50
+ #
51
+ # ValidatesCaptcha.middleware = MyMiddleware.new
52
+ #
53
+ class Simple
54
+ # Returns the captcha image path for a given encrypted code.
55
+ #
56
+ # This is used by the +captcha_image+ form helper.
57
+ def image_path(encrypted_code)
58
+ "/captchas/#{encrypted_code}#{ValidatesCaptcha.captcha_image_file_extension}"
59
+ end
60
+
61
+ # Returns the path that is used when requesting the regeneration
62
+ # of a captcha image. Defaults to '/captchas/regenerate'.
63
+ #
64
+ # This is used by the +regenerate_captcha_link+ form helper.
65
+ def regenerate_path
66
+ '/captchas/regenerate'
67
+ end
68
+
69
+ # This method is the one called by Rack.
70
+ #
71
+ # It returns HTTP status 404 if the path is not recognized. If the path is
72
+ # recognized, it returns HTTP status 200 and delivers the image if it could
73
+ # successfully decrypt the captcha code, otherwise HTTP status 422.
74
+ #
75
+ # Please take a look at the source code if you want to learn more.
76
+ def call(env)
77
+ if env['PATH_INFO'] =~ /^\/captchas\/([^\.]+)/
78
+ if $1 == 'regenerate'
79
+ encrypted_code = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
80
+ json = { :encrypted_captcha_code => encrypted_code, :captcha_image_path => image_path(encrypted_code) }.to_json
81
+
82
+ [200, { 'Content-Type' => 'application/json' }, [json]]
83
+ else
84
+ decrypted_code = ValidatesCaptcha.decrypt_captcha_code($1)
85
+
86
+ if decrypted_code.nil?
87
+ [422, { 'Content-Type' => 'text/html' }, ['Unprocessable Entity']]
88
+ else
89
+ image_data = ValidatesCaptcha.generate_captcha_image(decrypted_code)
90
+
91
+ response_headers = {
92
+ 'Content-Length' => image_data.bytesize.to_s,
93
+ 'Content-Type' => ValidatesCaptcha.captcha_image_mime_type,
94
+ 'Content-Disposition' => 'inline',
95
+ 'Content-Transfer-Encoding' => 'binary',
96
+ 'Cache-Control' => 'private'
97
+ }
98
+
99
+ [200, response_headers, [image_data]]
100
+ end
101
+ end
102
+ else
103
+ [404, { 'Content-Type' => 'text/html' }, ['Not Found']]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,66 @@
1
+ module ValidatesCaptcha
2
+ module ModelValidation
3
+ def self.included(base) #:nodoc:
4
+ base.extend ClassMethods
5
+ base.send :include, InstanceMethods
6
+
7
+ base.class_eval do
8
+ attr_accessible :captcha, :encrypted_captcha
9
+ attr_accessor :captcha
10
+ attr_writer :encrypted_captcha
11
+
12
+ validate :validate_captcha, :if => :validate_captcha?
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ # Activates captcha validation on entering the block and deactivates
18
+ # captcha validation on leaving the block.
19
+ #
20
+ # Example:
21
+ #
22
+ # User.with_captcha_validation do
23
+ # @user = User.new(...)
24
+ # @user.save
25
+ # end
26
+ def with_captcha_validation(&block)
27
+ self.validate_captcha = true
28
+ result = yield
29
+ self.validate_captcha = false
30
+ result
31
+ end
32
+
33
+ # Returns +true+ if captcha validation is activated, otherwise +false+.
34
+ def validate_captcha? #:nodoc:
35
+ @validate_captcha == true
36
+ end
37
+
38
+ private
39
+ def validate_captcha=(value) #:nodoc:
40
+ @validate_captcha = value
41
+ end
42
+ end
43
+
44
+ module InstanceMethods #:nodoc:
45
+ def encrypted_captcha #:nodoc:
46
+ return @encrypted_captcha unless @encrypted_captcha.blank?
47
+ @encrypted_captcha = ValidatesCaptcha.encrypt_captcha_code(ValidatesCaptcha.generate_captcha_code)
48
+ end
49
+
50
+ private
51
+ def validate_captcha? #:nodoc:
52
+ self.class.validate_captcha?
53
+ end
54
+
55
+ def validate_captcha #:nodoc:
56
+ errors.add(:captcha, :blank) and return if captcha.blank?
57
+ errors.add(:captcha, :invalid) unless captcha_valid?
58
+ end
59
+
60
+ def captcha_valid? #:nodoc:
61
+ ValidatesCaptcha.encrypt_captcha_code(captcha) == encrypted_captcha
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,54 @@
1
+ require 'openssl'
2
+ require 'active_support/secure_random'
3
+
4
+ module ValidatesCaptcha
5
+ module ReversibleEncrypter
6
+ # This class is responsible for encrypting and decrypting captcha codes.
7
+ # It internally uses AES256 to do the string encryption/decryption.
8
+ #
9
+ # You can implement your own reversible encrypter by creating a class
10
+ # that conforms to the method definitions of the example below and
11
+ # assign an instance of it to ValidatesCaptcha#reversible_encrypter=.
12
+ #
13
+ # Example for a custom encrypter/decrypter:
14
+ #
15
+ # class ReverseString # very insecure and easily cracked
16
+ # def encrypt(code)
17
+ # code.reverse
18
+ # end
19
+ #
20
+ # def decrypt(encrypted_code)
21
+ # encrypted_code.reverse
22
+ # rescue SomeKindOfDecryptionError
23
+ # nil
24
+ # end
25
+ # end
26
+ #
27
+ # ValidatesCaptcha.reversible_encrypter = ReverseString.new
28
+ #
29
+ # Please note: The #decrypt method should return +nil+ if decryption fails.
30
+ class Simple
31
+ KEY = ::ActiveSupport::SecureRandom.hex(32).freeze
32
+
33
+ def initialize #:nodoc:
34
+ @aes = OpenSSL::Cipher::Cipher.new('AES-256-ECB')
35
+ end
36
+
37
+ # Encrypts a cleartext string using #key as encryption key.
38
+ def encrypt(code)
39
+ @aes.encrypt
40
+ @aes.key = KEY
41
+ [@aes.update(code) + @aes.final].pack("m").tr('+/=', '-_ ').strip.gsub("\n", '')
42
+ end
43
+
44
+ # Decrypts an encrypted string using using #key as decryption key.
45
+ def decrypt(encrypted_code)
46
+ @aes.decrypt
47
+ @aes.key = KEY
48
+ @aes.update((encrypted_code + '=' * (4 - encrypted_code.size % 4)).tr('-_', '+/').unpack("m").first) + @aes.final
49
+ rescue # OpenSSL::CipherError, OpenSSL::Cipher::CipherError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,77 @@
1
+ module ValidatesCaptcha
2
+ module StringGenerator
3
+ # This class is responsible for generating the codes that are displayed
4
+ # on the captcha images. It does so by randomly selecting a number of
5
+ # characters from a predefined alphabet constisting of visually distinguishable
6
+ # letters and digits.
7
+ #
8
+ # The number of characters and the alphabet used when generating strings can
9
+ # be customized. See the #alphabet= and #length= methods for details.
10
+ #
11
+ # You can implement your own string generator by creating a
12
+ # class that conforms to the method definitions of the example below and
13
+ # assign an instance of it to ValidatesCaptcha#string_generator=.
14
+ #
15
+ # Example for a custom string generator:
16
+ #
17
+ # class DictionaryGenerator
18
+ # DICTIONARY = ['foo', 'bar', 'baz', ...]
19
+ #
20
+ # def generate
21
+ # return DICTIONARY[rand(DICTIONARY.size)]
22
+ # end
23
+ # end
24
+ #
25
+ # ValidatesCaptcha.string_generator = DictionaryGenerator.new
26
+ #
27
+ class Simple
28
+ @@alphabet = 'abdefghjkmnqrtABDEFGHJKLMNQRT234678923467892346789'
29
+ @@length = 6
30
+
31
+ class << self
32
+ # Returns a string holding the chars used when randomly generating the text that
33
+ # is displayed on a captcha image. Defaults to a string of visually distinguishable
34
+ # letters and digits.
35
+ def alphabet
36
+ @@alphabet
37
+ end
38
+
39
+ # Sets the string to use as alphabet when randomly generating the text displayed
40
+ # on a captcha image. To increase the probability of appearing in the image, some
41
+ # characters might appear more than once in the string.
42
+ #
43
+ # You can set this to a custom alphabet within a Rails initializer:
44
+ #
45
+ # ValidatesCaptcha::StringGenerator::Simple.alphabet = '01'
46
+ def alphabet=(alphabet)
47
+ alphabet = alphabet.to_s.gsub(/\s/, '')
48
+ raise('alphabet cannot be blank') if alphabet.blank?
49
+
50
+ @@alphabet = alphabet
51
+ end
52
+
53
+ # Returns the length to use when generating captcha codes. Defaults to 6.
54
+ def length
55
+ @@length
56
+ end
57
+
58
+ # Sets the length to use when generating captcha codes.
59
+ #
60
+ # You can set this to a custom length within a Rails initializer:
61
+ #
62
+ # ValidatesCaptcha::StringGenerator::Simple.length = 8
63
+ def length=(length)
64
+ @@length = length.to_i
65
+ end
66
+ end
67
+
68
+ # Randomly generates a string to be used as the code displayed on captcha images.
69
+ def generate
70
+ alphabet_chars = self.class.alphabet.split(//)
71
+ code_chars = []
72
+ self.class.length.times { code_chars << alphabet_chars[rand(alphabet_chars.size)] }
73
+ code_chars.join
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support/test_case'
2
+
3
+ module ValidatesCaptcha #:nodoc:
4
+ class TestCase < ActiveSupport::TestCase #:nodoc:
5
+ def assert_greater_than(expected, size, message = '')
6
+ _wrap_assertion do
7
+ full_message = build_message(message, "<?> expected to be greater than <?>.", size, expected)
8
+ assert_block(full_message) { size > expected }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module ValidatesCaptcha #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 9
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,170 @@
1
+ #--
2
+ # Copyright (c) 2009 Martin Andert
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+
25
+ # This module contains the getters and setters for the backend classes:
26
+ # image/string generator, reversible encrypter, and Rack middleware. This
27
+ # allows you to replace them with your custom implementations. For more
28
+ # information on how to bring Validates Captcha to use your own
29
+ # implementation instead of the default one, consult the documentation
30
+ # for the specific default class.
31
+ #
32
+ # This module also contains convenience wrapper methods for all the
33
+ # methods provided by the configured backend classes. These wrapper
34
+ # methods form the API that is visible to the outside world and that
35
+ # all backend classes use for internal communication.
36
+ module ValidatesCaptcha
37
+ autoload :ModelValidation, 'validates_captcha/model_validation'
38
+ autoload :ControllerValidation, 'validates_captcha/controller_validation'
39
+ autoload :FormHelper, 'validates_captcha/form_helper'
40
+ autoload :FormBuilder, 'validates_captcha/form_builder'
41
+ autoload :TestCase, 'validates_captcha/test_case'
42
+ autoload :VERSION, 'validates_captcha/version'
43
+
44
+ module ImageGenerator
45
+ autoload :Simple, 'validates_captcha/image_generator/simple'
46
+ end
47
+
48
+ module StringGenerator
49
+ autoload :Simple, 'validates_captcha/string_generator/simple'
50
+ end
51
+
52
+ module ReversibleEncrypter
53
+ autoload :Simple, 'validates_captcha/reversible_encrypter/simple'
54
+ end
55
+
56
+ module Middleware
57
+ autoload :Simple, 'validates_captcha/middleware/simple'
58
+ end
59
+
60
+ @@image_generator = nil
61
+ @@string_generator = nil
62
+ @@reversible_encrypter = nil
63
+ @@middleware = nil
64
+
65
+ class << self
66
+ # Returns ValidatesCaptcha's current version number.
67
+ def version
68
+ ValidatesCaptcha::VERSION::STRING
69
+ end
70
+
71
+ # Returns the current captcha image generator. Defaults to an
72
+ # instance of the ValidatesCaptcha::ImageGenerator::Simple class.
73
+ def image_generator
74
+ @@image_generator ||= ImageGenerator::Simple.new
75
+ end
76
+
77
+ # Sets the current captcha image generator. Used to set a custom
78
+ # image generator.
79
+ def image_generator=(generator)
80
+ @@image_generator = generator
81
+ end
82
+
83
+ # Returns the current captcha string generator. Defaults to an
84
+ # instance of the ValidatesCaptcha::StringGenerator::Simple class.
85
+ def string_generator
86
+ @@string_generator ||= StringGenerator::Simple.new
87
+ end
88
+
89
+ # Sets the current captcha string generator. Used to set a
90
+ # custom string generator.
91
+ def string_generator=(generator)
92
+ @@string_generator = generator
93
+ end
94
+
95
+ # Returns the current captcha reversible encrypter. Defaults to an
96
+ # instance of the ValidatesCaptcha::ReversibleEncrypter::Simple class.
97
+ def reversible_encrypter
98
+ @@reversible_encrypter ||= ReversibleEncrypter::Simple.new
99
+ end
100
+
101
+ # Sets the current captcha reversible encrypter. Used to set a
102
+ # custom reversible encrypter.
103
+ def reversible_encrypter=(encrypter)
104
+ @@reversible_encrypter = encrypter
105
+ end
106
+
107
+ # Returns the current captcha middleware. Defaults to the
108
+ # ValidatesCaptcha::Middleware::Simple class.
109
+ def middleware
110
+ @@middleware ||= Middleware::Simple.new
111
+ end
112
+
113
+ # Sets the current captcha middleware. Used to set a custom
114
+ # middleware.
115
+ def middleware=(middleware)
116
+ @@middleware = middleware
117
+ end
118
+
119
+ # Randomly generates a string which can be used as the code
120
+ # displayed on captcha images. This method internally calls
121
+ # +string_generator.generate+.
122
+ def generate_captcha_code
123
+ string_generator.generate
124
+ end
125
+
126
+ # Returns the image data of the generated captcha image. This
127
+ # method internally calls +image_generator.generate+.
128
+ def generate_captcha_image(code)
129
+ image_generator.generate(code)
130
+ end
131
+
132
+ # Returns the image file extension of the captcha images. This
133
+ # method internally calls +image_generator.image_file_extension+.
134
+ def captcha_image_file_extension
135
+ image_generator.image_file_extension
136
+ end
137
+
138
+ # Returns the image mime type of the captcha images. This
139
+ # method internally calls +image_generator.image_mime_type+.
140
+ def captcha_image_mime_type
141
+ image_generator.image_mime_type
142
+ end
143
+
144
+ # Returns the encryption of a cleartext captcha code. This
145
+ # method internally calls +reversible_encrypter.encrypt+.
146
+ def encrypt_captcha_code(code)
147
+ reversible_encrypter.encrypt(code)
148
+ end
149
+
150
+ # Returns the decryption of an encrypted captcha code. This
151
+ # method internally calls +reversible_encrypter.decrypt+.
152
+ def decrypt_captcha_code(encrypted_code)
153
+ reversible_encrypter.decrypt(encrypted_code)
154
+ end
155
+
156
+ # Returns the captcha image path for a given encrypted code. This
157
+ # method internally calls +middleware.image_path+.
158
+ def captcha_image_path(encrypted_code)
159
+ middleware.image_path(encrypted_code)
160
+ end
161
+
162
+ # Returns the path that is used when requesting the regeneration
163
+ # of a captcha image. This method internally calls
164
+ # +middleware.regenerate_path+.
165
+ def regenerate_captcha_path
166
+ middleware.regenerate_path
167
+ end
168
+ end
169
+ end
170
+
data/rails/init.rb ADDED
@@ -0,0 +1,29 @@
1
+ require 'validates_captcha'
2
+
3
+ config.after_initialize do
4
+ ::ActiveRecord::Base.send :include, ValidatesCaptcha::ModelValidation
5
+ ::ActionController::Base.send :include, ValidatesCaptcha::ControllerValidation
6
+ ::ActionView::Base.send :include, ValidatesCaptcha::FormHelper
7
+ ::ActionView::Helpers::FormBuilder.send :include, ValidatesCaptcha::FormBuilder
8
+ end
9
+
10
+ module ::ValidatesCaptcha
11
+ class MiddlewareWrapper
12
+ RECOGNIZED_RESPONSE_STATUS_CODES = [200, 422].freeze
13
+
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ def call(env)
19
+ result = ValidatesCaptcha.middleware.call(env)
20
+
21
+ return @app.call(env) unless RECOGNIZED_RESPONSE_STATUS_CODES.include?(result.first)
22
+
23
+ result
24
+ end
25
+ end
26
+ end
27
+
28
+ config.middleware.use ::ValidatesCaptcha::MiddlewareWrapper
29
+
@@ -0,0 +1,125 @@
1
+ require 'test_helper'
2
+ require 'action_controller'
3
+
4
+ ActionController::Routing::Routes.draw do |map|
5
+ map.connect ':controller/:action/:id'
6
+ end
7
+
8
+ class WidgetsController < ActionController::Base
9
+ include ValidatesCaptcha::ControllerValidation
10
+
11
+ validates_captcha :except => [:update, :save, :persist, :bingo]
12
+ validates_captcha_of :widgets, :only => :update
13
+ validates_captcha_of Widget, :only => [:save, :persist]
14
+
15
+ def create
16
+ begin
17
+ Widget.new.save!
18
+ rescue ActiveRecord::RecordInvalid
19
+ @invalid = true
20
+ end
21
+
22
+ render :nothing => true
23
+ end
24
+
25
+ def update
26
+ begin
27
+ Widget.new.save!
28
+ rescue ActiveRecord::RecordInvalid
29
+ @invalid = true
30
+ end
31
+
32
+ render :nothing => true
33
+ end
34
+
35
+ def save
36
+ encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
37
+ decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted)
38
+
39
+ begin
40
+ Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
41
+ rescue ActiveRecord::RecordInvalid
42
+ @invalid = true
43
+ end
44
+
45
+ render :nothing => true
46
+ end
47
+
48
+ def store
49
+ encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
50
+ decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted) + 'ha!'
51
+
52
+ begin
53
+ Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
54
+ rescue ActiveRecord::RecordInvalid
55
+ @invalid = true
56
+ end
57
+
58
+ render :nothing => true
59
+ end
60
+
61
+ def persist
62
+ encrypted = ValidatesCaptcha.encrypt_captcha_code('take this')
63
+ decrypted = ValidatesCaptcha.decrypt_captcha_code(encrypted) + 'ha!'
64
+
65
+ begin
66
+ Widget.create! :captcha => decrypted, :encrypted_captcha => encrypted
67
+ rescue ActiveRecord::RecordInvalid
68
+ @invalid = true
69
+ end
70
+
71
+ render :nothing => true
72
+ end
73
+
74
+ def bingo
75
+ begin
76
+ Widget.new.save!
77
+ rescue ActiveRecord::RecordInvalid
78
+ @invalid = true
79
+ end
80
+
81
+ render :nothing => true
82
+ end
83
+ end
84
+
85
+ class ControllerValidationTest < ActionController::TestCase
86
+ tests WidgetsController
87
+
88
+ test "defines a class level #validates_captcha method" do
89
+ assert_respond_to WidgetsController, :validates_captcha
90
+ end
91
+
92
+ test "defines a class level #validates_captcha_of method" do
93
+ assert_respond_to WidgetsController, :validates_captcha_of
94
+ end
95
+
96
+ test "calling #create method of controller should assign @invalid" do
97
+ post :create
98
+ assert_not_nil assigns(:invalid)
99
+ end
100
+
101
+ test "calling #update method of controller should assign @invalid" do
102
+ post :update
103
+ assert_not_nil assigns(:invalid)
104
+ end
105
+
106
+ test "calling #save method of controller should not assign @invalid" do
107
+ post :save
108
+ assert_nil assigns(:invalid)
109
+ end
110
+
111
+ test "calling #store method of controller should should assign @invalid" do
112
+ post :store
113
+ assert_not_nil assigns(:invalid)
114
+ end
115
+
116
+ test "calling #persist method of controller should should assign @invalid" do
117
+ post :persist
118
+ assert_not_nil assigns(:invalid)
119
+ end
120
+
121
+ test "calling #bingo method of controller should not assign @invalid" do
122
+ post :bingo
123
+ assert_nil assigns(:invalid)
124
+ end
125
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_helper'
2
+
3
+ IG = ValidatesCaptcha::ImageGenerator::Simple
4
+
5
+ class ImageGeneratorTest < ValidatesCaptcha::TestCase
6
+ test "defines an instance level #generate method" do
7
+ assert_respond_to IG.new, :generate
8
+ end
9
+
10
+ test "instance level #generate method accepts an argument" do
11
+ assert_nothing_raised { IG.new.generate('abc') }
12
+ end
13
+
14
+ test "instance level #generate method returns a string" do
15
+ assert_kind_of String, IG.new.generate('abc')
16
+ end
17
+
18
+ test "defines an instance level #image_file_extension method" do
19
+ assert_respond_to IG.new, :image_file_extension
20
+ end
21
+
22
+ test "instance level #image_file_extension method returns a string" do
23
+ assert_kind_of String, IG.new.image_file_extension
24
+ end
25
+
26
+ test "defines an instance level #image_mime_type method" do
27
+ assert_respond_to IG.new, :image_mime_type
28
+ end
29
+
30
+ test "instance level #image_mime_type method returns a string" do
31
+ assert_kind_of String, IG.new.image_mime_type
32
+ end
33
+ end