acts_as_textcaptcha 3.0.11 → 4.0.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.
- 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
|