acts_as_textcaptcha 3.0.11 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.travis.yml +4 -1
- data/README.rdoc +87 -78
- data/acts_as_textcaptcha.gemspec +4 -2
- data/config/textcaptcha.yml +5 -7
- data/lib/acts_as_textcaptcha/textcaptcha.rb +71 -91
- data/lib/acts_as_textcaptcha/textcaptcha_api.rb +61 -0
- data/lib/acts_as_textcaptcha/textcaptcha_cache.rb +34 -0
- data/lib/acts_as_textcaptcha/textcaptcha_helper.rb +9 -7
- data/lib/acts_as_textcaptcha/version.rb +1 -1
- data/lib/tasks/textcaptcha.rake +1 -6
- data/test/schema.rb +4 -0
- data/test/test_helper.rb +20 -2
- data/test/test_models.rb +15 -17
- data/test/textcaptcha_api_test.rb +50 -0
- data/test/textcaptcha_cache_test.rb +25 -0
- data/test/textcaptcha_helper_test.rb +31 -13
- data/test/textcaptcha_test.rb +54 -101
- metadata +167 -154
@@ -0,0 +1,61 @@
|
|
1
|
+
# simple wrapper for the textcaptcha.com API service
|
2
|
+
# loads and parses captcha question and answers
|
3
|
+
|
4
|
+
require 'rexml/document'
|
5
|
+
|
6
|
+
module ActsAsTextcaptcha
|
7
|
+
|
8
|
+
# raised if an empty response is returned
|
9
|
+
class EmptyResponseError < StandardError; end;
|
10
|
+
|
11
|
+
class TextcaptchaApi
|
12
|
+
|
13
|
+
ENDPOINT = 'http://textcaptcha.com/api/'
|
14
|
+
|
15
|
+
def self.fetch(api_key, options = {})
|
16
|
+
begin
|
17
|
+
url = uri_parser.parse("#{ENDPOINT}#{api_key}")
|
18
|
+
http = Net::HTTP.new(url.host, url.port)
|
19
|
+
if options[:http_open_timeout]
|
20
|
+
http.open_timeout = options[:http_open_timeout]
|
21
|
+
end
|
22
|
+
if options[:http_read_timeout]
|
23
|
+
http.read_timeout = options[:http_read_timeout]
|
24
|
+
end
|
25
|
+
|
26
|
+
response = http.get(url.path)
|
27
|
+
if response.body.empty?
|
28
|
+
raise ActsAsTextcaptcha::EmptyResponseError
|
29
|
+
else
|
30
|
+
return parse(response.body)
|
31
|
+
end
|
32
|
+
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
|
33
|
+
Errno::EHOSTUNREACH, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
34
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
35
|
+
URI::InvalidURIError, ActsAsTextcaptcha::EmptyResponseError,
|
36
|
+
REXML::ParseException
|
37
|
+
# rescue from these errors and continue
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse(xml)
|
42
|
+
parsed_xml = ActiveSupport::XmlMini.parse(xml)['captcha']
|
43
|
+
question = parsed_xml['question']['__content__']
|
44
|
+
if parsed_xml['answer'].is_a?(Array)
|
45
|
+
answers = parsed_xml['answer'].collect { |a| a['__content__'] }
|
46
|
+
else
|
47
|
+
answers = [parsed_xml['answer']['__content__']]
|
48
|
+
end
|
49
|
+
|
50
|
+
[question, answers]
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.uri_parser
|
57
|
+
# URI.parse is deprecated in 1.9.2
|
58
|
+
URI.const_defined?(:Parser) ? URI::Parser.new : URI
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# A simple cache for storing Textcaptcha answers
|
2
|
+
# by default the underlying cache implementation is
|
3
|
+
# the standard Rails.cache (ActiveSupport::Cache)
|
4
|
+
|
5
|
+
module ActsAsTextcaptcha
|
6
|
+
class TextcaptchaCache
|
7
|
+
|
8
|
+
CACHE_KEY_PREFIX = 'acts_as_textcaptcha-'
|
9
|
+
DEFAULT_CACHE_EXPIRY_MINUTES = 10
|
10
|
+
|
11
|
+
def write(key, value, options = {})
|
12
|
+
unless options.has_key?(:expires_in)
|
13
|
+
options[:expires_in] = DEFAULT_CACHE_EXPIRY_MINUTES.minutes
|
14
|
+
end
|
15
|
+
Rails.cache.write(cache_key(key), value, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(key, options = nil)
|
19
|
+
Rails.cache.read(cache_key(key), options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(key, options = nil)
|
23
|
+
Rails.cache.delete(cache_key(key), options)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# since this cache may be shared with other objects
|
29
|
+
# a prefix is used in all cache keys
|
30
|
+
def cache_key(key)
|
31
|
+
"#{CACHE_KEY_PREFIX}#{key}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -1,17 +1,19 @@
|
|
1
1
|
module ActsAsTextcaptcha
|
2
2
|
module TextcaptchaHelper
|
3
3
|
|
4
|
-
# builds html fields for
|
4
|
+
# builds html form fields for the textcaptcha
|
5
5
|
def textcaptcha_fields(f, &block)
|
6
6
|
model = f.object
|
7
7
|
captcha_html = ''
|
8
8
|
if model.perform_textcaptcha?
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
if model.textcaptcha_key
|
10
|
+
captcha_html += f.hidden_field(:textcaptcha_key)
|
11
|
+
if model.textcaptcha_question
|
12
|
+
captcha_html += capture(&block)
|
13
|
+
elsif model.textcaptcha_answer
|
14
|
+
captcha_html += f.hidden_field(:textcaptcha_answer)
|
15
|
+
end
|
16
|
+
end
|
15
17
|
end
|
16
18
|
|
17
19
|
# Rails 2 compatability
|
data/lib/tasks/textcaptcha.rake
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'bcrypt'
|
2
|
-
|
3
1
|
namespace :textcaptcha do
|
4
2
|
|
5
3
|
desc "Creates a template config file in config/textcaptcha.yml"
|
@@ -10,17 +8,14 @@ namespace :textcaptcha do
|
|
10
8
|
puts "\nOoops, a textcaptcha config file at #{dest} already exists ... aborting.\n\n"
|
11
9
|
else
|
12
10
|
config = ''
|
13
|
-
salt = BCrypt::Engine.generate_salt
|
14
11
|
f = File.open(src, 'r')
|
15
12
|
f.each_line { |line| config += line }
|
16
|
-
config.gsub!(/RAKE_GENERATED_SALT_PLACEHOLDER/, salt)
|
17
13
|
config.gsub!(/ api_key:(.*)# for gem test purposes only$/, " api_key: PASTE_YOUR_TEXTCAPCHA_API_KEY_HERE")
|
18
|
-
config.gsub!(/ bcrypt_salt:(.*)# for gem test purposes only$/, " bcrypt_salt: #{salt}")
|
19
14
|
|
20
15
|
f = File.new(dest, 'w')
|
21
16
|
f.write(config)
|
22
17
|
f.close
|
23
|
-
puts "\ntextcaptcha.yml generated at #{dest}
|
18
|
+
puts "\ntextcaptcha.yml generated at #{dest}\nNOTE: edit this file and add your textcaptcha api key, grab one from http://textcaptcha.com/api\n\n"
|
24
19
|
end
|
25
20
|
end
|
26
21
|
end
|
data/test/schema.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)+'./../lib'))
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)+'./../lib/acts_as_textcaptcha'))
|
2
2
|
|
3
3
|
ENV['RAILS_ENV'] = 'test'
|
4
4
|
|
5
|
+
# confgure test coverage reporting
|
5
6
|
if ENV['COVERAGE']
|
6
|
-
require
|
7
|
+
require 'simplecov'
|
7
8
|
SimpleCov.start do
|
8
9
|
add_filter '/test/'
|
10
|
+
add_filter '/vendor/'
|
9
11
|
end
|
10
12
|
SimpleCov.at_exit do
|
11
13
|
SimpleCov.result.format!
|
12
14
|
`open ./coverage/index.html` if RUBY_PLATFORM =~ /darwin/
|
13
15
|
end
|
16
|
+
elsif ENV['TRAVIS']
|
17
|
+
require 'coveralls'
|
18
|
+
Coveralls.wear!
|
14
19
|
end
|
15
20
|
|
16
21
|
require 'minitest/autorun'
|
@@ -18,9 +23,22 @@ require 'fakeweb'
|
|
18
23
|
|
19
24
|
require 'rails/all'
|
20
25
|
|
26
|
+
# silence warnings about I18n locales
|
27
|
+
I18n.config.enforce_available_locales = true
|
28
|
+
|
21
29
|
require 'acts_as_textcaptcha'
|
30
|
+
require 'textcaptcha_cache'
|
31
|
+
require 'textcaptcha_api'
|
22
32
|
require './test/test_models'
|
23
33
|
|
24
34
|
# load and initialize test db schema
|
25
35
|
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => 'acts_as_textcaptcha.sqlite3.db')
|
26
36
|
load(File.dirname(__FILE__) + "/schema.rb")
|
37
|
+
|
38
|
+
# initialize a Rails.cache (use a basic memory store in tests)
|
39
|
+
RAILS_CACHE = ActiveSupport::Cache::MemoryStore.new
|
40
|
+
|
41
|
+
# additional helper methods for use in tests
|
42
|
+
def find_in_cache(key)
|
43
|
+
RAILS_CACHE.read("#{ActsAsTextcaptcha::TextcaptchaCache::CACHE_KEY_PREFIX}#{key}")
|
44
|
+
end
|
data/test/test_models.rb
CHANGED
@@ -8,36 +8,36 @@ end
|
|
8
8
|
|
9
9
|
class Comment < ActiveRecord::Base
|
10
10
|
# inline options (symbol keys) with api_key only
|
11
|
-
acts_as_textcaptcha :api_key
|
12
|
-
|
11
|
+
acts_as_textcaptcha :api_key => '8u5ixtdnq9csc84cok0owswgo'
|
12
|
+
end
|
13
|
+
|
14
|
+
class FastComment < ActiveRecord::Base
|
15
|
+
# inline options with super fast (0.006 seconds) cache expiry time
|
16
|
+
acts_as_textcaptcha :cache_expiry_minutes => '0.0001',
|
17
|
+
:questions => [{ :question => '1+1', :answers => '2,two' }]
|
13
18
|
end
|
14
19
|
|
15
20
|
class Review < ActiveRecord::Base
|
16
21
|
# inline options with all possible options
|
17
|
-
acts_as_textcaptcha
|
18
|
-
'
|
19
|
-
'bcrypt_cost' => '3',
|
20
|
-
'questions' => [{ 'question' => 'The green hat is what color?', 'answers' => 'green' }]
|
22
|
+
acts_as_textcaptcha :api_key => '8u5ixtdnq9csc84cok0owswgo',
|
23
|
+
:questions => [{ :question => 'The green hat is what color?', :answers => 'green' }]
|
21
24
|
end
|
22
25
|
|
23
26
|
class MovieReview < ActiveRecord::Base
|
24
27
|
# inline options with all possible options
|
25
|
-
acts_as_textcaptcha 'api_key'
|
26
|
-
'
|
27
|
-
'bcrypt_cost' => '3',
|
28
|
-
'questions' => [{ 'Question' => 'The green hat is what color?', 'answers' => nil }]
|
28
|
+
acts_as_textcaptcha 'api_key' => '8u5ixtdnq9csc84cok0owswgo',
|
29
|
+
'questions' => [{ 'Question' => 'The green hat is what color?', 'answers' => nil }]
|
29
30
|
end
|
30
31
|
|
31
32
|
class Note < ActiveRecord::Base
|
32
33
|
# inline options (string keys) with user defined questions only (no textcaptcha service)
|
33
|
-
acts_as_textcaptcha 'questions'
|
34
|
-
'bcrypt_salt' => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
|
34
|
+
acts_as_textcaptcha 'questions' => [{ 'question' => '1+1', 'answers' => '2,two' }]
|
35
35
|
|
36
36
|
# allows toggling perform_textcaptcha on/off (default on)
|
37
37
|
attr_accessor :turn_off_captcha
|
38
38
|
|
39
39
|
def perform_textcaptcha?
|
40
|
-
!turn_off_captcha
|
40
|
+
super && !turn_off_captcha
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -46,8 +46,7 @@ class Contact
|
|
46
46
|
include ActiveModel::Validations
|
47
47
|
include ActiveModel::Conversion
|
48
48
|
extend ActsAsTextcaptcha::Textcaptcha
|
49
|
-
acts_as_textcaptcha :questions
|
50
|
-
:bcrypt_salt => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
|
49
|
+
acts_as_textcaptcha :questions => [{ :question => 'one+1', :answers => "2,two,апельсин" }]
|
51
50
|
end
|
52
51
|
|
53
52
|
# ActiveRecord model using the strong parameters gem
|
@@ -55,6 +54,5 @@ require 'strong_parameters'
|
|
55
54
|
|
56
55
|
class StrongWidget < ActiveRecord::Base
|
57
56
|
include ActiveModel::ForbiddenAttributesProtection
|
58
|
-
acts_as_textcaptcha 'questions'
|
59
|
-
'bcrypt_salt' => '$2a$10$j0bmycH.SVfD1b5mpEGPpe'
|
57
|
+
acts_as_textcaptcha 'questions' => [{ 'question' => '1+1', 'answers' => '2,two' }]
|
60
58
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+'/test_helper')
|
2
|
+
|
3
|
+
describe 'TextcaptchaApi' do
|
4
|
+
|
5
|
+
after(:each) do
|
6
|
+
FakeWeb.clean_registry
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'with a valid xml response' do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
body = "<captcha><question>1+1?</question><answer>1</answer><answer>2</answer><answer>3</answer></captcha>"
|
13
|
+
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/abc|, :body => body)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should fetch and parse an answer from the service' do
|
17
|
+
result = ActsAsTextcaptcha::TextcaptchaApi.fetch('abc')
|
18
|
+
result[0].must_equal '1+1?'
|
19
|
+
result[1].must_equal ['1', '2', '3']
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should allow http options to be set' do
|
23
|
+
result = ActsAsTextcaptcha::TextcaptchaApi.fetch('abc', { :http_read_timeout => 30,
|
24
|
+
:http_open_timeout => 5 })
|
25
|
+
result.length.must_equal 2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return nil when Net::HTTP errors occur' do
|
30
|
+
[
|
31
|
+
SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
|
32
|
+
Errno::EHOSTUNREACH, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
33
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
34
|
+
URI::InvalidURIError
|
35
|
+
].each do |error|
|
36
|
+
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/xyz|, :exception => error)
|
37
|
+
ActsAsTextcaptcha::TextcaptchaApi.fetch('xyz').must_equal nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should return nil when body cannot be parsed as XML' do
|
42
|
+
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/jibber|, :body => 'here be gibberish')
|
43
|
+
ActsAsTextcaptcha::TextcaptchaApi.fetch('jibber').must_equal nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should return nil when body is empty' do
|
47
|
+
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/empty|, :body => '')
|
48
|
+
ActsAsTextcaptcha::TextcaptchaApi.fetch('empty').must_equal nil
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)+'/test_helper')
|
2
|
+
|
3
|
+
describe 'TextcaptchaCache' do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@cache = ActsAsTextcaptcha::TextcaptchaCache.new
|
7
|
+
@cache.write('mykey', [1,2,3])
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should write to the cache' do
|
11
|
+
@cache.write('my-new-key', 'abc')
|
12
|
+
@cache.read('my-new-key').must_equal 'abc'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should read from the cache' do
|
16
|
+
@cache.read('mykey').must_equal [1,2,3]
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should delete from the cache' do
|
20
|
+
@cache.read('mykey').must_equal [1,2,3]
|
21
|
+
@cache.delete('mykey')
|
22
|
+
|
23
|
+
@cache.read('mykey').must_equal nil
|
24
|
+
end
|
25
|
+
end
|
@@ -17,13 +17,13 @@ describe 'TextcaptchaHelper' do
|
|
17
17
|
@note.textcaptcha
|
18
18
|
end
|
19
19
|
|
20
|
-
def render_template(assigns)
|
20
|
+
def render_template(assigns = { :note => @note })
|
21
21
|
template = <<-ERB
|
22
22
|
<%= form_for(@note, :url => '/') do |f| %>
|
23
23
|
<%= textcaptcha_fields(f) do %>
|
24
24
|
<div class="field textcaptcha">
|
25
|
-
<%= f.label :
|
26
|
-
<%= f.text_field :
|
25
|
+
<%= f.label :textcaptcha_answer, @note.textcaptcha_question %><br/>
|
26
|
+
<%= f.text_field :textcaptcha_answer, :value => '' %>
|
27
27
|
</div>
|
28
28
|
<% end %>
|
29
29
|
<% end %>
|
@@ -32,19 +32,37 @@ describe 'TextcaptchaHelper' do
|
|
32
32
|
Template.new([], assigns, @controller).render(:inline => template)
|
33
33
|
end
|
34
34
|
|
35
|
-
it 'should render question and answer fields, with hidden
|
36
|
-
html = render_template
|
35
|
+
it 'should render question and answer fields, with hidden textcaptcha_key field' do
|
36
|
+
html = render_template
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
assert_match /\<label for\=\"note_textcaptcha_answer\"\>1\+1\<\/label\>/, html
|
39
|
+
assert_match /\<input id\=\"note_textcaptcha_answer\" name\=\"note\[textcaptcha_answer\]\" size\=\"30\" type\=\"text\" value\=\"\" \/>/, html
|
40
|
+
assert_match /\<input id\=\"note_textcaptcha_key\" name\=\"note\[textcaptcha_key\]\" type\=\"hidden\" value\=\"([0-9a-f]{32})\" \/\>/, html
|
40
41
|
end
|
41
42
|
|
42
|
-
it 'should render hidden answer and
|
43
|
-
@note.
|
44
|
-
|
43
|
+
it 'should render hidden answer and textcaptcha_key when only answer is present' do
|
44
|
+
@note.textcaptcha_question = nil
|
45
|
+
@note.textcaptcha_answer = 2
|
46
|
+
html = render_template
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
refute_match /\<label for\=\"note_textcaptcha_answer\"\>1\+1\<\/label\>/, html
|
49
|
+
assert_match /\<input id\=\"note_textcaptcha_answer\" name\=\"note\[textcaptcha_answer\]\" type\=\"hidden\" value\=\"2\" \/>/, html
|
50
|
+
assert_match /\<input id\=\"note_textcaptcha_key\" name\=\"note\[textcaptcha_key\]\" type\=\"hidden\" value\=\"([0-9a-f]{32})\" \/\>/, html
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not render any question or answer when perform_textcaptcha? is false' do
|
54
|
+
@note.turn_off_captcha = true
|
55
|
+
html = render_template
|
56
|
+
|
57
|
+
refute_match /note_textcaptcha_answer/, html
|
58
|
+
refute_match /note_textcaptcha_key/, html
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should not render any question or answer when textcaptcha_key is missing' do
|
62
|
+
@note.textcaptcha_key = nil
|
63
|
+
html = render_template
|
64
|
+
|
65
|
+
refute_match /note_textcaptcha_answer/, html
|
66
|
+
refute_match /note_textcaptcha_key/, html
|
49
67
|
end
|
50
68
|
end
|
data/test/textcaptcha_test.rb
CHANGED
@@ -10,40 +10,40 @@ describe 'Textcaptcha' do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'should validate an ActiveRecord object (with multiple correct answers)' do
|
13
|
-
@note.
|
13
|
+
@note.textcaptcha_question.must_equal('1+1')
|
14
14
|
@note.valid?.must_equal false
|
15
|
-
@note.errors[:
|
15
|
+
@note.errors[:textcaptcha_answer].first.must_equal('is incorrect, try another question instead')
|
16
16
|
|
17
|
-
@note.
|
17
|
+
@note.textcaptcha_answer = 'two'
|
18
18
|
@note.valid?.must_equal true
|
19
|
-
@note.errors[:
|
19
|
+
@note.errors[:textcaptcha_answer].must_be_empty
|
20
20
|
|
21
|
-
@note.
|
21
|
+
@note.textcaptcha_answer = '2'
|
22
22
|
@note.valid?.must_equal true
|
23
|
-
@note.errors[:
|
23
|
+
@note.errors[:textcaptcha_answer].must_be_empty
|
24
24
|
end
|
25
25
|
|
26
|
-
it 'should strip whitespace and downcase
|
27
|
-
@note.
|
26
|
+
it 'should strip whitespace and downcase answer' do
|
27
|
+
@note.textcaptcha_answer = ' tWo '
|
28
28
|
@note.valid?.must_equal true
|
29
|
-
@note.errors[:
|
29
|
+
@note.errors[:textcaptcha_answer].must_be_empty
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'should always be valid when record has been saved' do
|
33
|
-
@note.
|
33
|
+
@note.textcaptcha_answer = '2'
|
34
34
|
@note.save!
|
35
35
|
@note.textcaptcha
|
36
36
|
|
37
|
-
@note.
|
37
|
+
@note.textcaptcha_answer = 'wrong answer'
|
38
38
|
@note.new_record?.must_equal false
|
39
39
|
@note.valid?.must_equal true
|
40
|
-
@note.errors[:
|
40
|
+
@note.errors[:textcaptcha_answer].must_be_empty
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'should always be valid when perform_textcaptcha? is false' do
|
44
44
|
@note.turn_off_captcha = true
|
45
45
|
@note.valid?.must_equal true
|
46
|
-
@note.errors[:
|
46
|
+
@note.errors[:textcaptcha_answer].must_be_empty
|
47
47
|
|
48
48
|
@note.save.must_equal true
|
49
49
|
end
|
@@ -52,24 +52,13 @@ describe 'Textcaptcha' do
|
|
52
52
|
@contact = Contact.new
|
53
53
|
@contact.textcaptcha
|
54
54
|
|
55
|
-
@contact.
|
56
|
-
@contact.
|
55
|
+
@contact.textcaptcha_question.must_equal('one+1')
|
56
|
+
@contact.textcaptcha_answer = 'wrong'
|
57
57
|
@contact.valid?.must_equal false
|
58
58
|
|
59
|
-
@contact.
|
59
|
+
@contact.textcaptcha_answer = 'two'
|
60
60
|
@contact.valid?.must_equal true
|
61
|
-
@contact.errors[:
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'should allow validation to be skipped' do
|
65
|
-
@note.valid?.must_equal false
|
66
|
-
@note.skip_textcaptcha = true
|
67
|
-
@note.valid?.must_equal true
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'should protect skip_textcaptcha attribute from mass assignment' do
|
71
|
-
@note = Note.new(:skip_textcaptcha => true)
|
72
|
-
@note.skip_textcaptcha.must_equal nil
|
61
|
+
@contact.errors[:textcaptcha_answer].must_be_empty
|
73
62
|
end
|
74
63
|
end
|
75
64
|
|
@@ -80,36 +69,31 @@ describe 'Textcaptcha' do
|
|
80
69
|
end
|
81
70
|
|
82
71
|
it 'should work' do
|
83
|
-
@widget.spam_answers.must_be_nil
|
84
72
|
@widget.textcaptcha
|
85
|
-
@widget.
|
73
|
+
@widget.textcaptcha_question.must_equal('1+1')
|
86
74
|
@widget.valid?.must_equal false
|
87
|
-
@widget.errors[:
|
75
|
+
@widget.errors[:textcaptcha_answer].first.must_equal('is incorrect, try another question instead')
|
88
76
|
|
89
|
-
@widget.
|
77
|
+
@widget.textcaptcha_answer = 'two'
|
90
78
|
@widget.valid?.must_equal true
|
91
|
-
@widget.errors[:
|
79
|
+
@widget.errors[:textcaptcha_answer].must_be_empty
|
92
80
|
end
|
93
81
|
end
|
94
82
|
|
95
|
-
describe '
|
83
|
+
describe 'with fast expiry time' do
|
96
84
|
|
97
85
|
before(:each) do
|
98
|
-
@
|
86
|
+
@comment = FastComment.new
|
99
87
|
end
|
100
88
|
|
101
|
-
it 'should
|
102
|
-
@
|
103
|
-
@
|
104
|
-
|
105
|
-
|
106
|
-
@note.spam_answers.must_equal(encrypted_answers)
|
107
|
-
end
|
89
|
+
it 'should work' do
|
90
|
+
@comment.textcaptcha
|
91
|
+
@comment.textcaptcha_question.must_equal('1+1')
|
92
|
+
@comment.textcaptcha_answer = 'two'
|
93
|
+
sleep(0.01)
|
108
94
|
|
109
|
-
|
110
|
-
@
|
111
|
-
proc { @note.textcaptcha }.must_raise BCrypt::Errors::InvalidSalt
|
112
|
-
@note.textcaptcha_config[:bcrypt_salt] ='$2a$10$j0bmycH.SVfD1b5mpEGPpe'
|
95
|
+
@comment.valid?.must_equal false
|
96
|
+
@comment.errors[:textcaptcha_answer].first.must_equal('was not submitted quickly enough, try another question instead')
|
113
97
|
end
|
114
98
|
end
|
115
99
|
|
@@ -119,17 +103,17 @@ describe 'Textcaptcha' do
|
|
119
103
|
FakeWeb.clean_registry
|
120
104
|
end
|
121
105
|
|
122
|
-
it 'should generate
|
106
|
+
it 'should generate a question from the service' do
|
123
107
|
@review = Review.new
|
124
108
|
|
125
109
|
@review.textcaptcha
|
126
|
-
@review.
|
127
|
-
@review.
|
110
|
+
@review.textcaptcha_question.wont_be_nil
|
111
|
+
@review.textcaptcha_question.wont_equal('The green hat is what color?')
|
128
112
|
|
129
|
-
@review.
|
113
|
+
find_in_cache(@review.textcaptcha_key).wont_be_nil
|
130
114
|
|
131
115
|
@review.valid?.must_equal false
|
132
|
-
@review.errors[:
|
116
|
+
@review.errors[:textcaptcha_answer].first.must_equal('is incorrect, try another question instead')
|
133
117
|
end
|
134
118
|
|
135
119
|
it 'should parse a single answer from XML response' do
|
@@ -139,9 +123,8 @@ describe 'Textcaptcha' do
|
|
139
123
|
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => body)
|
140
124
|
|
141
125
|
@review.textcaptcha
|
142
|
-
@review.
|
143
|
-
@review.
|
144
|
-
@review.spam_answers.split('-').length.must_equal(1)
|
126
|
+
@review.textcaptcha_question.must_equal(question)
|
127
|
+
find_in_cache(@review.textcaptcha_key).must_equal(['f6f7fec07f372b7bd5eb196bbca0f3f4'])
|
145
128
|
end
|
146
129
|
|
147
130
|
it 'should parse multiple answers from XML response' do
|
@@ -151,76 +134,46 @@ describe 'Textcaptcha' do
|
|
151
134
|
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => body)
|
152
135
|
|
153
136
|
@review.textcaptcha
|
154
|
-
@review.
|
155
|
-
@review.
|
137
|
+
@review.textcaptcha_question.must_equal(question)
|
138
|
+
find_in_cache(@review.textcaptcha_key).length.must_equal(3)
|
156
139
|
end
|
157
140
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
|
166
|
-
it 'when errors occur' do
|
167
|
-
[SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
|
168
|
-
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, URI::InvalidURIError].each do |error|
|
169
|
-
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :exception => error)
|
170
|
-
@review.textcaptcha
|
171
|
-
@review.spam_question.must_equal('The green hat is what color?')
|
172
|
-
@review.spam_answers.wont_be_nil
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
it 'when response is OK but body cannot be parsed as XML' do
|
177
|
-
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => 'here be gibberish')
|
178
|
-
@review.textcaptcha
|
179
|
-
@review.spam_question.must_equal('The green hat is what color?')
|
180
|
-
@review.spam_answers.wont_be_nil
|
181
|
-
end
|
182
|
-
|
183
|
-
it 'when response is OK but empty' do
|
184
|
-
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => '')
|
185
|
-
@review.textcaptcha
|
186
|
-
@review.spam_question.must_equal('The green hat is what color?')
|
187
|
-
@review.spam_answers.wont_be_nil
|
188
|
-
end
|
189
|
-
end
|
141
|
+
it 'should fallback to a user defined question when api returns nil' do
|
142
|
+
@review = Review.new
|
143
|
+
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :body => '')
|
144
|
+
@review.textcaptcha
|
145
|
+
@review.textcaptcha_question.must_equal('The green hat is what color?')
|
146
|
+
find_in_cache(@review.textcaptcha_key).wont_be_nil
|
190
147
|
end
|
191
148
|
|
192
|
-
it 'should not generate any
|
149
|
+
it 'should not generate any question or answer when no user defined questions set' do
|
193
150
|
@comment = Comment.new
|
194
151
|
|
195
152
|
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :exception => SocketError)
|
196
153
|
@comment.textcaptcha
|
197
|
-
@comment.
|
198
|
-
@comment.
|
154
|
+
@comment.textcaptcha_question.must_equal nil
|
155
|
+
@comment.textcaptcha_key.must_equal nil
|
199
156
|
end
|
200
157
|
|
201
|
-
it 'should not generate any
|
158
|
+
it 'should not generate any question or answer when user defined questions set incorrectly' do
|
202
159
|
@comment = MovieReview.new
|
203
160
|
|
204
161
|
FakeWeb.register_uri(:get, %r|http://textcaptcha\.com/api/|, :exception => SocketError)
|
205
162
|
@comment.textcaptcha
|
206
|
-
@comment.
|
207
|
-
@comment.
|
163
|
+
@comment.textcaptcha_question.must_equal nil
|
164
|
+
@comment.textcaptcha_key.must_equal nil
|
208
165
|
end
|
209
166
|
end
|
210
167
|
|
211
168
|
describe 'configuration' do
|
212
169
|
|
213
170
|
it 'should be configured with inline hash' do
|
214
|
-
Review.textcaptcha_config.must_equal({ :api_key
|
215
|
-
:
|
216
|
-
:bcrypt_cost => '3',
|
217
|
-
:questions => [{ 'question' => 'The green hat is what color?', 'answers' => 'green' }]})
|
171
|
+
Review.textcaptcha_config.must_equal({ :api_key => '8u5ixtdnq9csc84cok0owswgo',
|
172
|
+
:questions => [{ :question => 'The green hat is what color?', :answers => 'green' }]})
|
218
173
|
end
|
219
174
|
|
220
175
|
it 'should be configured with textcaptcha.yml' do
|
221
|
-
Widget.textcaptcha_config[:api_key].must_equal
|
222
|
-
Widget.textcaptcha_config[:bcrypt_salt].must_equal '$2a$10$qhSefD6gKtmq6M0AzXk4CO'
|
223
|
-
Widget.textcaptcha_config[:bcrypt_cost].must_equal 1
|
176
|
+
Widget.textcaptcha_config[:api_key].must_equal '6eh1co0j12mi2ogcoggkkok4o'
|
224
177
|
Widget.textcaptcha_config[:questions].length.must_equal 10
|
225
178
|
end
|
226
179
|
end
|