twimock 0.0.1

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +125 -0
  8. data/Rakefile +6 -0
  9. data/db/.gitkeep +0 -0
  10. data/lib/twimock/access_token.rb +31 -0
  11. data/lib/twimock/api/account/verify_credentials.rb +40 -0
  12. data/lib/twimock/api/application.rb +29 -0
  13. data/lib/twimock/api/intent/sessions.rb +60 -0
  14. data/lib/twimock/api/oauth/access_token.rb +65 -0
  15. data/lib/twimock/api/oauth/authenticate.rb +51 -0
  16. data/lib/twimock/api/oauth/request_token.rb +49 -0
  17. data/lib/twimock/api/oauth.rb +83 -0
  18. data/lib/twimock/api.rb +35 -0
  19. data/lib/twimock/application.rb +21 -0
  20. data/lib/twimock/auth_hash.rb +8 -0
  21. data/lib/twimock/config.rb +90 -0
  22. data/lib/twimock/database/table.rb +359 -0
  23. data/lib/twimock/database.rb +133 -0
  24. data/lib/twimock/errors.rb +13 -0
  25. data/lib/twimock/omniauth/strategies/twitter.rb +28 -0
  26. data/lib/twimock/omniauth_twitter.rb +36 -0
  27. data/lib/twimock/request_token.rb +23 -0
  28. data/lib/twimock/user.rb +58 -0
  29. data/lib/twimock/version.rb +3 -0
  30. data/lib/twimock.rb +39 -0
  31. data/spec/spec_helper.rb +18 -0
  32. data/spec/support/api_spec_helper.rb +30 -0
  33. data/spec/support/omniauth_twitter_helper.rb +26 -0
  34. data/spec/support/tables_helper.rb +54 -0
  35. data/spec/support/test_application_helper.rb +9 -0
  36. data/spec/twimock/access_token_spec.rb +128 -0
  37. data/spec/twimock/api/account/verify_credentials_spec.rb +125 -0
  38. data/spec/twimock/api/application_spec.rb +27 -0
  39. data/spec/twimock/api/intent/sessions_spec.rb +184 -0
  40. data/spec/twimock/api/oauth/access_token_spec.rb +185 -0
  41. data/spec/twimock/api/oauth/authenticate_spec.rb +96 -0
  42. data/spec/twimock/api/oauth/request_token_spec.rb +123 -0
  43. data/spec/twimock/api_spec.rb +81 -0
  44. data/spec/twimock/application_spec.rb +120 -0
  45. data/spec/twimock/auth_hash_spec.rb +7 -0
  46. data/spec/twimock/config_spec.rb +192 -0
  47. data/spec/twimock/database/table_spec.rb +769 -0
  48. data/spec/twimock/database_spec.rb +261 -0
  49. data/spec/twimock/omniauth_twitter_spec.rb +129 -0
  50. data/spec/twimock/request_token_spec.rb +140 -0
  51. data/spec/twimock/user_spec.rb +271 -0
  52. data/spec/twimock_spec.rb +76 -0
  53. data/twimock.gemspec +38 -0
  54. data/view/authenticate.html.erb +23 -0
  55. metadata +343 -0
@@ -0,0 +1,58 @@
1
+ require 'faker'
2
+ require 'twimock/database/table'
3
+ require 'twimock/access_token'
4
+ require 'twimock/request_token'
5
+
6
+ module Twimock
7
+ # TODO: 要改善 AccessTokenをUserから分離
8
+ class User < Database::Table
9
+ TABLE_NAME = :users
10
+ COLUMN_NAMES = [:id, :name, :twitter_id, :email, :password, :created_at]
11
+ CHILDREN = [ Twimock::AccessToken, Twimock::RequestToken ]
12
+ INFO_KEYS = [:id, :name, :created_at]
13
+
14
+ def initialize(options={})
15
+ opts = Hashie::Mash.new(options)
16
+ id = opts.id || opts.identifier
17
+ @id = (id.to_i > 0) ? id.to_i : (Faker::Number.number(10)).to_i
18
+ @name = opts.name || create_user_name
19
+ @twitter_id = opts.twitter_id || @name.downcase.gsub(" ", "_")
20
+ @email = opts.email || Faker::Internet.email
21
+ @password = opts.password || Faker::Internet.password
22
+ @created_at = opts.created_at
23
+ end
24
+
25
+ def info
26
+ info_hash = Hashie::Mash.new({})
27
+ INFO_KEYS.each { |key| info_hash[key] = self.instance_variable_get("@#{key}") }
28
+ info_hash.id_str = info_hash.id.to_s
29
+ info_hash
30
+ end
31
+
32
+ def generate_access_token(application_id=nil)
33
+ if application_id
34
+ application = Twimock::Application.find_by_id(application_id)
35
+ raise Twimock::Errors::ApplicationNotFound unless application
36
+ end
37
+
38
+ access_token = Twimock::AccessToken.new({ application_id: application_id })
39
+ if self.persisted?
40
+ access_token.user_id = self.id
41
+ access_token.save!
42
+ end
43
+ access_token
44
+ end
45
+
46
+ def self.find_by_tiwtter_id_or_email(value)
47
+ user = Twimock::User.find_by_twitter_id(value)
48
+ user ||= Twimock::User.find_by_email(value)
49
+ end
50
+
51
+ private
52
+
53
+ def create_user_name
54
+ n = Faker::Name.name
55
+ (n.include?("'") || n.include?(".")) ? create_user_name : n
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ module Twimock
2
+ VERSION = "0.0.1"
3
+ end
data/lib/twimock.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "active_support/time"
2
+ require "twimock/version"
3
+ require "twimock/database"
4
+ require "twimock/config"
5
+ require "twimock/application"
6
+ require "twimock/user"
7
+ require "twimock/access_token"
8
+ require "twimock/request_token"
9
+ require "twimock/auth_hash"
10
+ require "twimock/errors"
11
+ require "twimock/api"
12
+ require "twimock/omniauth_twitter"
13
+
14
+ module Twimock
15
+ extend self
16
+
17
+ def auth_hash(access_token_string=nil)
18
+ return Twimock::AuthHash.new unless validate_access_token_string(access_token_string)
19
+
20
+ if access_token = Twimock::AccessToken.find_by_string(access_token_string)
21
+ if user = Twimock::User.find_by_id(access_token.user_id)
22
+ hash = Twimock::AuthHash.new({
23
+ provider: "twitter",
24
+ uid: user.id,
25
+ info: { name: user.name },
26
+ credentials: { token: access_token.string, expires_at: Time.now + 60.days },
27
+ extra: { raw_info: { id: user.id, name: user.name } }
28
+ })
29
+ end
30
+ end
31
+ hash || Twimock::AuthHash.new
32
+ end
33
+
34
+ private
35
+
36
+ def validate_access_token_string(string)
37
+ string.kind_of?(String) && string.size > 0
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'simplecov'
4
+ require 'coveralls'
5
+
6
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
7
+ SimpleCov::Formatter::HTMLFormatter,
8
+ Coveralls::SimpleCov::Formatter
9
+ ]
10
+ SimpleCov.start do
11
+ add_filter '.bundle/'
12
+ add_filter '/spec/'
13
+ end
14
+
15
+ # spec/support 以下のヘルパーを読み込み
16
+ Dir[File.expand_path('../support/', __FILE__) + '/**/*.rb'].each {|f| require f}
17
+
18
+ require 'twimock'
@@ -0,0 +1,30 @@
1
+ module APISpecHelper
2
+ shared_examples 'TestRackApplication 200 OK' do
3
+ it 'should return 200 OK' do
4
+ expect(last_response.status).to eq 200
5
+ expect(last_response.body).to be_blank
6
+ expect(last_response.header).to be_blank
7
+ end
8
+ end
9
+
10
+ shared_examples 'API 401 UnAuthorized' do
11
+ it 'should return 401 Unauthorized' do
12
+ expect(last_response.status).to eq 401
13
+ expect(last_response.header).not_to be_blank
14
+ expect(last_response.header['Content-Length']).to eq last_response.body.bytesize.to_s
15
+ expect(last_response.header['Content-Type']).to eq "application/json; charset=utf-8"
16
+ expect(last_response.body).not_to be_blank
17
+ parsed_body = JSON.parse(last_response.body)
18
+ expect(parsed_body["error"]["code"]).to match /^Invalid.*/
19
+ end
20
+ end
21
+
22
+ shared_examples 'API 500 InternalServerError' do
23
+ it 'should return 500' do
24
+ expect(last_response.status).to eq 500
25
+ expect(last_response.header).not_to be_blank
26
+ expect(last_response.header['Content-Length']).to eq last_response.body.bytesize.to_s
27
+ expect(last_response.body).not_to be_blank
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ module OmniAuthTwitterHelper
2
+ def create_request_env(request_token, request_secret)
3
+ {
4
+ 'rack.input' => StringIO.new(""),
5
+ 'rack.session' => { "session_id" => "123456",
6
+ "callback_path" => "/",
7
+ "omniauth.params" => {},
8
+ "omniauth.origin" => "http://example.com/authentication",
9
+ "oauth" => { "twitter" => { "callback_confirmed" => true,
10
+ "request_token" => request_token,
11
+ "request_secret" => request_secret } } },
12
+ 'rack.url_scheme' => "http",
13
+ "GATEWAY_INTERFACE" => "CGI/1.1",
14
+ "PATH_INFO" => "/users/auth/twitter",
15
+ "QUERY_STRING" => "",
16
+ "REMOTE_ADDR" => "127.0.0.1",
17
+ "REMOTE_HOST" => "localhost.localdomain",
18
+ "REQUEST_METHOD" => "GET",
19
+ "REQUEST_URI" => "http://example.com/users/auth/twitter",
20
+ "SERVER_NAME" => "example.com",
21
+ "SERVER_PORT" => "80",
22
+ "SERVER_PROTOCOL" => "HTTP/1.1",
23
+ "HTTP_HOST" => "example.com",
24
+ }
25
+ end
26
+ end
@@ -0,0 +1,54 @@
1
+ module TableHelper
2
+ def remove_dynamically_defined_all_method
3
+ klasses = [Twimock::Database::Table]
4
+ klasses.each do |klass|
5
+ remove_dynamically_defined_class_method(klass)
6
+ remove_dynamically_defined_instance_method(klass)
7
+ end
8
+ end
9
+
10
+ # テストで動的に定義したクラスメソッドを削除
11
+ def remove_dynamically_defined_class_method(klass)
12
+ klass.methods.each do |method_name|
13
+ if method_name.to_s =~ /^find_by_/ || method_name.to_s =~ /^find_all_by_/
14
+ klass.class_eval do
15
+ class_variable_set(:@@target_method_name, method_name)
16
+ class << self
17
+ remove_method class_variable_get(:@@target_method_name)
18
+ end
19
+ remove_class_variable(:@@target_method_name)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # テストで動的に定義したインスタンスメソッドを削除
26
+ def remove_dynamically_defined_instance_method(klass)
27
+ klass.column_names.each do |column_name|
28
+ getter = column_name
29
+ if klass.instance_methods.include?(getter)
30
+ klass.class_eval { remove_method getter }
31
+ end
32
+
33
+ setter = (column_name.to_s + "=").to_sym
34
+ if klass.instance_methods.include?(setter)
35
+ klass.class_eval { remove_method setter }
36
+ end
37
+ end
38
+ end
39
+
40
+ # Tableクラスでテストするために、一時的にDB Tableを作成する
41
+ def create_tables_table_for_test
42
+ db = Twimock::Database.new
43
+ db.connection.execute <<-SQL
44
+ CREATE TABLE TABLES (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ text TEXT NOT NULL,
47
+ active BOOLEAN,
48
+ number INTEGER NOT NULL,
49
+ created_at DATETIME NOT NULL
50
+ );
51
+ SQL
52
+ db.disconnect!
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ module TestApplicationHelper
2
+ extend self
3
+
4
+ class TestRackApplication
5
+ def call(env)
6
+ [ 200, {}, [] ]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Twimock::AccessToken do
4
+ include TableHelper
5
+
6
+ let(:db_name) { ".test" }
7
+ let(:table_name) { :access_tokens }
8
+ let(:column_names) { [ :id, :string, :secret, :application_id, :user_id, :created_at ] }
9
+
10
+ let(:id) { 1 }
11
+ let(:string) { "test_token" }
12
+ let(:secret) { "test_token_secret" }
13
+ let(:application_id) { 1 }
14
+ let(:user_id) { 1 }
15
+ let(:created_at) { Time.now }
16
+ let(:options) { { id: id,
17
+ string: string,
18
+ secret: secret,
19
+ application_id: application_id,
20
+ user_id: user_id,
21
+ created_at: created_at } }
22
+
23
+ after { remove_dynamically_defined_all_method }
24
+
25
+ describe '::TABLE_NAME' do
26
+ subject { Twimock::AccessToken::TABLE_NAME }
27
+ it { is_expected.to eq table_name }
28
+ end
29
+
30
+ describe '::COLUMN_NAMES' do
31
+ subject { Twimock::AccessToken::COLUMN_NAMES }
32
+ it { is_expected.to eq column_names }
33
+ end
34
+
35
+ describe '#initialize' do
36
+ context 'without option' do
37
+ subject { Twimock::AccessToken.new }
38
+ it { is_expected.to be_kind_of Twimock::AccessToken }
39
+
40
+ describe '.id' do
41
+ subject { Twimock::AccessToken.new.id }
42
+ it { is_expected.to be_nil }
43
+ end
44
+
45
+ describe '.string' do
46
+ subject { Twimock::AccessToken.new.string }
47
+ it { is_expected.to be_kind_of String }
48
+
49
+ describe '.size' do
50
+ subject { Twimock::AccessToken.new.string.size }
51
+ it { is_expected.to eq 50 }
52
+ end
53
+ end
54
+
55
+ describe '.secret' do
56
+ subject { Twimock::AccessToken.new.secret }
57
+ it { is_expected.to be_kind_of String }
58
+
59
+ describe '.size' do
60
+ subject { Twimock::AccessToken.new.secret.size }
61
+ it { is_expected.to eq 45 }
62
+ end
63
+ end
64
+
65
+ describe '.user_id' do
66
+ subject { Twimock::AccessToken.new.user_id }
67
+ it { is_expected.to be_nil }
68
+ end
69
+
70
+ describe '.application_id' do
71
+ subject { Twimock::AccessToken.new.application_id }
72
+ it { is_expected.to be_nil }
73
+ end
74
+
75
+ describe '.created_at' do
76
+ subject { Twimock::AccessToken.new.created_at }
77
+ it { is_expected.to be_nil }
78
+ end
79
+ end
80
+
81
+ context 'with id option but it is not integer' do
82
+ before { @opts = { id: "test_id" } }
83
+ subject { Twimock::AccessToken.new(@opts) }
84
+ it { is_expected.to be_kind_of Twimock::AccessToken }
85
+
86
+ describe '.id' do
87
+ subject { Twimock::AccessToken.new(@opts).id }
88
+ it { is_expected.to be_nil }
89
+ end
90
+ end
91
+
92
+ context 'with user_id option but it is not integer' do
93
+ before { @opts = { user_id: "test_id" } }
94
+ subject { Twimock::AccessToken.new(@opts) }
95
+ it { is_expected.to be_kind_of Twimock::AccessToken }
96
+
97
+ describe '.user_id' do
98
+ subject { Twimock::AccessToken.new(@opts).user_id }
99
+ it { is_expected.to be_nil }
100
+ end
101
+ end
102
+
103
+ context 'with application_id option but it is not integer' do
104
+ before { @opts = { application_id: "test_id" } }
105
+ subject { Twimock::AccessToken.new(@opts) }
106
+ it { is_expected.to be_kind_of Twimock::AccessToken }
107
+
108
+ describe '.application_id' do
109
+ subject { Twimock::AccessToken.new(@opts).application_id }
110
+ it { is_expected.to be_nil }
111
+ end
112
+ end
113
+
114
+ context 'with all options' do
115
+ subject { Twimock::AccessToken.new(options) }
116
+ it { is_expected.to be_kind_of Twimock::AccessToken }
117
+
118
+ context 'then attributes' do
119
+ it 'should set specified value by option' do
120
+ column_names.each do |column_name|
121
+ value = Twimock::AccessToken.new(options).send(column_name)
122
+ expect(value).to eq options[column_name]
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ describe Twimock::API::Account::VerifyCredentials do
5
+ include TestApplicationHelper
6
+ include Rack::Test::Methods
7
+
8
+ let(:method) { 'GET' }
9
+ let(:path) { '/1.1/account/verify_credentials.json' }
10
+ let(:authorization_regexp) { Regexp.new('OAuth oauth_consumer_key=\"(.*)\", oauth_nonce=\"(.*)\", oauth_signature=\"(.*)\", oauth_signature_method=\"(.*)\", oauth_timestamp=\"(.*)\", oauth_token=\"(.*)\", oauth_version=\"(.*)\".*') }
11
+
12
+ let(:oauth_consumer_key) { Twimock::Application.new.api_key }
13
+ let(:oauth_nonce) { "Tc400qacfXAoixQ5Tk9yeFjdBBrDb7U3Sdgs7WA8cM" }
14
+ let(:oauth_signature) { "I7LRwjN%2FRvqp53kia2fGCg%2FrBHo%3D" }
15
+ let(:oauth_signature_method) { "HMAC-SHA1" }
16
+ let(:oauth_timestamp) { "1422273906" }
17
+ let(:oauth_token) { Twimock::AccessToken.string }
18
+ let(:oauth_version) { "1.0" }
19
+ let(:authorization_header) { [ "OAuth oauth_consumer_key=\"#{oauth_consumer_key}\", oauth_nonce=\"#{oauth_nonce}\", oauth_signature=\"#{oauth_signature}\", oauth_signature_method=\"#{oauth_signature_method}\", oauth_timestamp=\"#{oauth_timestamp}\", oauth_token=\"#{oauth_token}\", oauth_version=\"#{oauth_version}\"" ] }
20
+
21
+ let(:body) { "" }
22
+ let(:header) { {} }
23
+ let(:test_app) { TestApplicationHelper::TestRackApplication.new }
24
+ let(:app) { Twimock::API::Account::VerifyCredentials.new(test_app) }
25
+
26
+ describe '::METHOD' do
27
+ subject { Twimock::API::Account::VerifyCredentials::METHOD }
28
+ it { is_expected.to eq method }
29
+ end
30
+
31
+ describe '::PATH' do
32
+ subject { Twimock::API::Account::VerifyCredentials::PATH }
33
+ it { is_expected.to eq path }
34
+ end
35
+
36
+ describe '::AUTHORIZATION_REGEXP' do
37
+ subject { Twimock::API::Account::VerifyCredentials::AUTHORIZATION_REGEXP }
38
+ it { is_expected.to eq authorization_regexp }
39
+ end
40
+
41
+ describe "GET '/1.1/account/verify_credentials.json'" do
42
+ context 'with authorization header' do
43
+ let(:db_name) { ".test" }
44
+ let(:database) { Twimock::Database.new }
45
+
46
+ before { stub_const("Twimock::Database::DEFAULT_DB_NAME", db_name) }
47
+ after { database.drop }
48
+
49
+ context 'that is correct' do
50
+ let(:header) { { "authorization" => authorization_header } }
51
+ let(:oauth_consumer_key) { @application.api_key }
52
+ let(:oauth_token) { @access_token.string }
53
+
54
+ before do
55
+ @application = Twimock::Application.new
56
+ @application.save!
57
+ @user = Twimock::User.new
58
+ @user.save!
59
+ @access_token = @user.generate_access_token(@application.id)
60
+ end
61
+
62
+ it 'should return 200 OK' do
63
+ get path, body, header
64
+
65
+ expect(last_response.status).to eq 200
66
+ expect(last_response.header).not_to be_blank
67
+ expect(last_response.header['Content-Length']).to eq last_response.body.bytesize.to_s
68
+ expect(last_response.body).not_to be_blank
69
+
70
+ # bodyの検証
71
+ parsed_body = JSON.parse(last_response.body)
72
+ expect(parsed_body['id']).to eq @user.id
73
+ expect(parsed_body['id_str']).to eq @user.id.to_s
74
+ expect(parsed_body['name']).to eq @user.name
75
+ expect(parsed_body['created_at']).to eq @user.created_at.to_s
76
+ end
77
+ end
78
+
79
+ context 'that is incorrect format' do
80
+ let(:consumer_key) { "test_consumer_key" }
81
+ before { get path, body, header }
82
+ it_behaves_like 'API 401 UnAuthorized'
83
+ end
84
+
85
+ context 'but consumer_key is invalid' do
86
+ before { get path, body, header }
87
+ it_behaves_like 'API 401 UnAuthorized'
88
+ end
89
+ end
90
+
91
+ context 'without authorization header', assert: :UnauthorizedAccountVerifyCredentials do
92
+ let(:authorization_header) { nil }
93
+ before { get path, body, header }
94
+ it_behaves_like 'API 401 UnAuthorized'
95
+ end
96
+
97
+ context 'raise error that is not catched' do
98
+ let(:header) { { "authorization" => authorization_header } }
99
+ let(:oauth_consumer_key) { @application.api_key }
100
+ let(:oauth_token) { @access_token.string }
101
+
102
+ before do
103
+ @application = Twimock::Application.new
104
+ @application.save!
105
+ @user = Twimock::User.new
106
+ @user.save!
107
+ @access_token = @user.generate_access_token(@application.id)
108
+ allow(Twimock::User).to receive(:find_by_id) { raise }
109
+ get path, body, header
110
+ end
111
+
112
+ it_behaves_like 'API 500 InternalServerError'
113
+ end
114
+ end
115
+
116
+ describe "GET '/test'" do
117
+ before { get '/test' }
118
+ it_behaves_like 'TestRackApplication 200 OK'
119
+ end
120
+
121
+ describe "POST '/1.1/account/verify_credentials.json'" do
122
+ before { post '/1.1/account/verify_credentials.json' }
123
+ it_behaves_like 'TestRackApplication 200 OK'
124
+ end
125
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Twimock::API::Application do
4
+ before do
5
+ allow_any_instance_of(Excon::Connection).to receive(:request) do
6
+ options = { status: status, headers: headers, body: body }
7
+ response = Excon::Response.new(options)
8
+ end
9
+ end
10
+
11
+ let(:status) { 200 }
12
+ let(:headers) { { "Content-Length" => 4, "Content-Type"=>"text/plain" } }
13
+ let(:body) { "test_body" }
14
+
15
+ let(:env) { { "rack.input" => StringIO.new(body),
16
+ "REQUEST_METHOD" => "GET",
17
+ "SERVER_NAME" => "api.twitter.com",
18
+ "SERVER_PORT" => "443",
19
+ "QUERY_STRING" => "",
20
+ "PATH_INFO" => "/",
21
+ "HTTPS" => "on" } }
22
+
23
+ describe 'call' do
24
+ subject { Twimock::API::Application.new.call(env) }
25
+ it { is_expected.to eq [ status, headers, [ body ] ] }
26
+ end
27
+ end