validates_captcha 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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