ruby-fs-stack 0.1.7
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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +26 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/examples/familytree_example.rb +26 -0
- data/examples/login_example.rb +11 -0
- data/lib/ruby-fs-stack/assets/entrust-ca.crt +104 -0
- data/lib/ruby-fs-stack/enunciate/LICENSE +15 -0
- data/lib/ruby-fs-stack/enunciate/README +6 -0
- data/lib/ruby-fs-stack/enunciate/familytree.rb +12608 -0
- data/lib/ruby-fs-stack/enunciate/identity.rb +964 -0
- data/lib/ruby-fs-stack/familytree.rb +827 -0
- data/lib/ruby-fs-stack/fs_communicator.rb +109 -0
- data/lib/ruby-fs-stack/fs_utils.rb +27 -0
- data/lib/ruby-fs-stack/identity.rb +45 -0
- data/lib/ruby-fs-stack/warning_suppressor.rb +18 -0
- data/lib/ruby-fs-stack.rb +2 -0
- data/spec/communicator_spec.rb +214 -0
- data/spec/familytree_v2/familytree_communicator_spec.rb +309 -0
- data/spec/familytree_v2/json/match_KW3B-NNM.js +1 -0
- data/spec/familytree_v2/json/person/KJ86-3VD_all.js +1 -0
- data/spec/familytree_v2/json/person/KJ86-3VD_version.js +1 -0
- data/spec/familytree_v2/json/person/post_response.js +1 -0
- data/spec/familytree_v2/json/person/relationship_not_found.js +1 -0
- data/spec/familytree_v2/json/person/relationship_read.js +1 -0
- data/spec/familytree_v2/json/person/relationship_update.js +1 -0
- data/spec/familytree_v2/json/search.js +1 -0
- data/spec/familytree_v2/person_spec.rb +563 -0
- data/spec/familytree_v2/search_results_spec.rb +131 -0
- data/spec/fs_utils_spec.rb +33 -0
- data/spec/identity_v1/identity_spec.rb +50 -0
- data/spec/identity_v1/json/login.js +1 -0
- data/spec/ruby-fs-stack_spec.rb +6 -0
- data/spec/spec_helper.rb +27 -0
- metadata +119 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class FsCommunicator
|
5
|
+
attr_accessor :domain, :key, :user_agent, :session, :handle_throttling
|
6
|
+
|
7
|
+
# ====Params
|
8
|
+
# <tt>options</tt> - a hash with the following options
|
9
|
+
# * :domain - Defaults to "http://www.dev.usys.org" (the Reference System)
|
10
|
+
# * :key - Your developer key. Defaults to ''
|
11
|
+
# * :user_agent - Your User-Agent string. This should be overridden by your app. It
|
12
|
+
# defaults to "FsCommunicator/0.1 (Ruby)"
|
13
|
+
# * :session - A session string if you already have one.
|
14
|
+
# * :handle_throttling - (true|false) Defaults to false. If true, when a 503 response
|
15
|
+
# is received from the API, it will sleep 15 seconds, and try again until successful.
|
16
|
+
# You will likely want this turned off when running this library from Rails or any other
|
17
|
+
# system that is single-threaded so as to not sleep the entire process until throttling
|
18
|
+
# is successful.
|
19
|
+
def initialize(options = {})
|
20
|
+
# merge default options with options hash
|
21
|
+
o = {
|
22
|
+
:domain => 'http://www.dev.usys.org',
|
23
|
+
:key => '',
|
24
|
+
:user_agent => 'FsCommunicator/0.1 (Ruby)', # should be overridden by options user_agent
|
25
|
+
:session => nil,
|
26
|
+
:handle_throttling => false
|
27
|
+
}.merge(options)
|
28
|
+
@domain = o[:domain]
|
29
|
+
@key = o[:key]
|
30
|
+
@user_agent = o[:user_agent]
|
31
|
+
@session = o[:session]
|
32
|
+
@handle_throttling = o[:handle_throttling]
|
33
|
+
end
|
34
|
+
|
35
|
+
def post(url,payload)
|
36
|
+
uri = URI.parse(self.domain+url)
|
37
|
+
full_url = set_extra_params(uri)
|
38
|
+
request = Net::HTTP::Post.new(full_url)
|
39
|
+
request.body = payload
|
40
|
+
request['Content-Type'] = "application/json"
|
41
|
+
request['User-Agent'] = self.user_agent
|
42
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
43
|
+
if uri.scheme == 'https'
|
44
|
+
http.use_ssl = true
|
45
|
+
http.ca_file = File.join File.dirname(__FILE__), 'assets','entrust-ca.crt'
|
46
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
47
|
+
end
|
48
|
+
res = http.start do |ht|
|
49
|
+
ht.request(request)
|
50
|
+
end
|
51
|
+
if res.code == '503' && @handle_throttling
|
52
|
+
sleep 15
|
53
|
+
res = post(url,payload)
|
54
|
+
end
|
55
|
+
return res
|
56
|
+
end
|
57
|
+
|
58
|
+
def get(url,credentials = {})
|
59
|
+
uri = URI.parse(self.domain+url)
|
60
|
+
full_url = set_extra_params(uri,credentials)
|
61
|
+
request = Net::HTTP::Get.new(full_url)
|
62
|
+
request['User-Agent'] = self.user_agent
|
63
|
+
if credentials[:username] && credentials[:password]
|
64
|
+
request.basic_auth credentials[:username], credentials[:password]
|
65
|
+
end
|
66
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
67
|
+
if uri.scheme == 'https'
|
68
|
+
http.use_ssl = true
|
69
|
+
http.ca_file = File.join File.dirname(__FILE__), 'assets','entrust-ca.crt'
|
70
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
71
|
+
end
|
72
|
+
res = http.start do |ht|
|
73
|
+
ht.request(request)
|
74
|
+
end
|
75
|
+
if res.code == '503' && @handle_throttling
|
76
|
+
sleep 15
|
77
|
+
res = get(url,credentials)
|
78
|
+
end
|
79
|
+
return res
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def set_extra_params(uri,credentials = {})
|
84
|
+
if credentials[:username] && credentials[:password]
|
85
|
+
sessionized_url = add_key(uri)
|
86
|
+
else
|
87
|
+
sessionized_url = add_session(uri)
|
88
|
+
end
|
89
|
+
sessionized_url << '&dataFormat=application/json'
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_session(uri)
|
93
|
+
if uri.query
|
94
|
+
uri.query << '&sessionId=' + self.session
|
95
|
+
else
|
96
|
+
uri.query = 'sessionId=' + self.session
|
97
|
+
end
|
98
|
+
uri.request_uri
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_key(uri)
|
102
|
+
if uri.query
|
103
|
+
uri.query << '&key=' + self.key
|
104
|
+
else
|
105
|
+
uri.query = 'key=' + self.key
|
106
|
+
end
|
107
|
+
uri.request_uri
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class FsUtils
|
2
|
+
|
3
|
+
def self.querystring_from_hash(hash)
|
4
|
+
params = hash.map do |k,v|
|
5
|
+
k = k.to_s
|
6
|
+
if v.is_a? Hash
|
7
|
+
v.collect do |k2,v2|
|
8
|
+
k2 = k2.to_s
|
9
|
+
v2 = v2.to_s
|
10
|
+
url_encode(v2)
|
11
|
+
"#{k}.#{k2}=#{v2}"
|
12
|
+
end.join('&')
|
13
|
+
else
|
14
|
+
v = v.to_s
|
15
|
+
self.url_encode(v)
|
16
|
+
k + '=' + v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
params.join('&')
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def self.url_encode(string)
|
24
|
+
# Taken from http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/156044
|
25
|
+
string.gsub!( /[^a-zA-Z0-9\-_\.!~*'()]/n ) {|x| sprintf('%%%02x', x[0]) }
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ruby-fs-stack/fs_communicator'
|
3
|
+
# Including more than one enunciate library raises a warning of
|
4
|
+
# already initialized constant.
|
5
|
+
require 'ruby-fs-stack/warning_suppressor'
|
6
|
+
with_warnings_suppressed do
|
7
|
+
require 'ruby-fs-stack/enunciate/identity'
|
8
|
+
end
|
9
|
+
|
10
|
+
module IdentityV1
|
11
|
+
|
12
|
+
# This method gets mixed into the FsCommunicator so that
|
13
|
+
# you can make calls on the fs_familytree_v1 module
|
14
|
+
def identity_v1
|
15
|
+
@identity_v1_com ||= Communicator.new self # self at this point refers to the FsCommunicator instance
|
16
|
+
end
|
17
|
+
|
18
|
+
class Communicator
|
19
|
+
Base = '/identity/v1/'
|
20
|
+
|
21
|
+
# ====params
|
22
|
+
# fs_communicator: FsCommunicator instance
|
23
|
+
def initialize(fs_communicator)
|
24
|
+
@communicator = fs_communicator
|
25
|
+
end
|
26
|
+
|
27
|
+
# ==== Params
|
28
|
+
# <tt>credentials</tt> - :username, :password
|
29
|
+
def authenticate(credentials = {})
|
30
|
+
url = Base + 'login'
|
31
|
+
response = @communicator.get(url, credentials)
|
32
|
+
login_result = Org::Familysearch::Ws::Identity::V1::Schema::Identity.from_json JSON.parse(response.body)
|
33
|
+
if login_result.statusCode == 200
|
34
|
+
@communicator.session = login_result.session.id
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
# Mix in the module so that the identity_v1 can be called
|
43
|
+
class FsCommunicator
|
44
|
+
include IdentityV1
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Kernel#with_warnings_suppressed - Supresses warnings in a given block.
|
2
|
+
# Require this file to use it or run it directly to perform a self-test.
|
3
|
+
#
|
4
|
+
# Author:: Rob Pitt
|
5
|
+
# Copyright:: Copyright (c) 2008 Rob Pitt
|
6
|
+
# License:: Free to use and modify so long as credit to previous author(s) is left in place.
|
7
|
+
#
|
8
|
+
|
9
|
+
module Kernel
|
10
|
+
# Suppresses warnings within a given block.
|
11
|
+
def with_warnings_suppressed
|
12
|
+
saved_verbosity = $-v
|
13
|
+
$-v = nil
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
$-v = saved_verbosity
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'ruby-fs-stack/fs_communicator'
|
3
|
+
|
4
|
+
describe FsCommunicator do
|
5
|
+
include HttpCommunicatorHelper
|
6
|
+
describe "initializing" do
|
7
|
+
it "should accept a hash of options" do
|
8
|
+
lambda {
|
9
|
+
com = FsCommunicator.new :domain => 'https://api.familysearch.org', :key => '1111-1111', :user_agent => "FsCommunicator/0.1"
|
10
|
+
}.should_not raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set defaults to the Reference System" do
|
14
|
+
com = FsCommunicator.new
|
15
|
+
com.domain.should == 'http://www.dev.usys.org'
|
16
|
+
com.key.should == ''
|
17
|
+
com.user_agent.should == 'FsCommunicator/0.1 (Ruby)'
|
18
|
+
com.handle_throttling.should == false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the domain, key, and user_agent to options hash" do
|
22
|
+
options = {
|
23
|
+
:domain => 'https://api.familysearch.org',
|
24
|
+
:key => '1111-1111',
|
25
|
+
:user_agent => "RSpecTest/0.1",
|
26
|
+
:session => 'SESSID',
|
27
|
+
:handle_throttling => true
|
28
|
+
}
|
29
|
+
com = FsCommunicator.new options
|
30
|
+
com.domain.should == options[:domain]
|
31
|
+
com.key.should == options[:key]
|
32
|
+
com.user_agent.should == options[:user_agent]
|
33
|
+
com.session.should == options[:session]
|
34
|
+
com.handle_throttling.should == true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "GET on a URL" do
|
39
|
+
before(:each) do
|
40
|
+
options = {
|
41
|
+
:domain => 'https://api.familysearch.org',
|
42
|
+
:key => '1111-1111',
|
43
|
+
:user_agent => "FsCommunicator/0.1",
|
44
|
+
:session => 'SESSID'
|
45
|
+
}
|
46
|
+
@com = FsCommunicator.new options
|
47
|
+
stub_net_objects #found in the spec helper
|
48
|
+
@url = '/familytree/v1/person/KWQS-BBQ'
|
49
|
+
@session_url = @url + "?sessionId=#{@com.session}&dataFormat=application/json"
|
50
|
+
@res = mock('HTTP::Response')
|
51
|
+
@res.stub!(:code).and_return('200')
|
52
|
+
@http.stub!(:start).and_return(@res)
|
53
|
+
end
|
54
|
+
|
55
|
+
def do_get(url, credentials = {})
|
56
|
+
@com.get(url, credentials)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should initialize a Net::HTTP object to make the request" do
|
60
|
+
Net::HTTP.should_receive(:new).with('api.familysearch.org',443).and_return(@http)
|
61
|
+
do_get(@url)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should create a GET request with url containing a session" do
|
65
|
+
Net::HTTP::Get.should_receive(:new).with(@session_url).and_return(@request)
|
66
|
+
do_get(@url)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should tack a sessionId as an additional parameter if params already set" do
|
70
|
+
url = "/familytree/v1/person/KWQS-BBQ?view=summary"
|
71
|
+
Net::HTTP::Get.should_receive(:new).with(url+"&sessionId=#{@com.session}&dataFormat=application/json").and_return(@request)
|
72
|
+
do_get(url)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should set the http object to use ssl if https" do
|
76
|
+
@http.should_receive(:use_ssl=).with(true)
|
77
|
+
do_get(@url)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should not set the http object to use ssl if no http" do
|
81
|
+
@com.domain = 'http://www.dev.usys.org'
|
82
|
+
@http.should_not_receive(:use_ssl=)
|
83
|
+
do_get(@url)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should set the ca file to the entrust certificate (for FamilySearch systems)" do
|
87
|
+
@http.should_receive(:ca_file=).with(File.join(File.dirname(__FILE__),'..','lib','ruby-fs-stack','assets','entrust-ca.crt'))
|
88
|
+
do_get(@url)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should set the basic_authentication if the credentials passed as parameters" do
|
92
|
+
@request.should_receive(:basic_auth).with('user','pass')
|
93
|
+
@request.should_receive(:[]=).with('User-Agent',@com.user_agent)
|
94
|
+
Net::HTTP::Get.should_receive(:new).with(@url+"?key=#{@com.key}&dataFormat=application/json").and_return(@request)
|
95
|
+
do_get(@url,:username => 'user',:password => 'pass')
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should make the request" do
|
99
|
+
block = lambda{ |ht|
|
100
|
+
ht.request('something')
|
101
|
+
}
|
102
|
+
@http.should_receive(:start)
|
103
|
+
do_get(@url)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should sleep and call again if handle_throttling is set to true and the response code is 503" do
|
107
|
+
@com.handle_throttling = true
|
108
|
+
@res.stub!(:code).and_return('503','200')
|
109
|
+
@http.stub!(:start).and_return(@res)
|
110
|
+
@com.should_receive(:sleep).once
|
111
|
+
do_get(@url)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should not call sleep if handle_throttling is set to false" do
|
115
|
+
@com.handle_throttling = false
|
116
|
+
@res.stub!(:code).and_return('503','200')
|
117
|
+
@http.stub!(:start).and_return(@res)
|
118
|
+
@com.should_not_receive(:sleep)
|
119
|
+
do_get(@url)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "POST on a URL" do
|
125
|
+
before(:each) do
|
126
|
+
options = {
|
127
|
+
:domain => 'https://api.familysearch.org',
|
128
|
+
:key => '1111-1111',
|
129
|
+
:user_agent => "FsCommunicator/0.1",
|
130
|
+
:session => 'SESSID'
|
131
|
+
}
|
132
|
+
@com = FsCommunicator.new options
|
133
|
+
stub_net_objects
|
134
|
+
@url = '/familytree/v1/person/KWQS-BBQ'
|
135
|
+
@session_url = @url + "?sessionId=#{@com.session}&dataFormat=application/json"
|
136
|
+
@payload = "<familytree></familytree>"
|
137
|
+
@res = mock('HTTP::Response')
|
138
|
+
@res.stub!(:code).and_return('200')
|
139
|
+
@http.stub!(:start).and_return(@res)
|
140
|
+
end
|
141
|
+
|
142
|
+
def do_post(url, payload = '')
|
143
|
+
@com.post(url, payload)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should initialize a Net::HTTP object to make the request" do
|
147
|
+
Net::HTTP.should_receive(:new).with('api.familysearch.org',443).and_return(@http)
|
148
|
+
do_post(@url)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should create a POST request with url containing a session" do
|
152
|
+
Net::HTTP::Post.should_receive(:new).with(@session_url).and_return(@request)
|
153
|
+
do_post(@url)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should tack a sessionId as an additional parameter if params already set" do
|
157
|
+
url = "/familytree/v1/person/KWQS-BBQ?view=summary"
|
158
|
+
Net::HTTP::Post.should_receive(:new).with(url+"&sessionId=#{@com.session}&dataFormat=application/json").and_return(@request)
|
159
|
+
do_post(url)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should set the request's body to the payload attached" do
|
163
|
+
@request.should_receive(:body=).with(@payload)
|
164
|
+
do_post(@url,@payload)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should set the request's Content-Type to application/json" do
|
168
|
+
@request.should_receive(:[]=).with('Content-Type','application/json')
|
169
|
+
do_post(@url,@payload)
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should set the http object to use ssl if https" do
|
173
|
+
@http.should_receive(:use_ssl=).with(true)
|
174
|
+
do_post(@url)
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should not set the http object to use ssl if no http" do
|
178
|
+
@com.domain = 'http://www.dev.usys.org'
|
179
|
+
@http.should_not_receive(:use_ssl=)
|
180
|
+
do_post(@url)
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should set the ca file to the entrust certificate (for FamilySearch systems)" do
|
184
|
+
@http.should_receive(:ca_file=).with(File.join(File.dirname(__FILE__),'..','lib','ruby-fs-stack','assets','entrust-ca.crt'))
|
185
|
+
do_post(@url)
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should make the request" do
|
189
|
+
block = lambda{ |ht|
|
190
|
+
ht.request('something')
|
191
|
+
}
|
192
|
+
@http.should_receive(:start)
|
193
|
+
do_post(@url)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should sleep and call again if handle_throttling is set to true and the response code is 503" do
|
197
|
+
@com.handle_throttling = true
|
198
|
+
@res.stub!(:code).and_return('503','200')
|
199
|
+
@http.stub!(:start).and_return(@res)
|
200
|
+
@com.should_receive(:sleep).once
|
201
|
+
do_post(@url)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should not call sleep if handle_throttling is set to false" do
|
205
|
+
@com.handle_throttling = false
|
206
|
+
@res.stub!(:code).and_return('503','200')
|
207
|
+
@http.stub!(:start).and_return(@res)
|
208
|
+
@com.should_not_receive(:sleep)
|
209
|
+
do_post(@url)
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|