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.
Files changed (37) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +26 -0
  5. data/Rakefile +51 -0
  6. data/VERSION +1 -0
  7. data/examples/familytree_example.rb +26 -0
  8. data/examples/login_example.rb +11 -0
  9. data/lib/ruby-fs-stack/assets/entrust-ca.crt +104 -0
  10. data/lib/ruby-fs-stack/enunciate/LICENSE +15 -0
  11. data/lib/ruby-fs-stack/enunciate/README +6 -0
  12. data/lib/ruby-fs-stack/enunciate/familytree.rb +12608 -0
  13. data/lib/ruby-fs-stack/enunciate/identity.rb +964 -0
  14. data/lib/ruby-fs-stack/familytree.rb +827 -0
  15. data/lib/ruby-fs-stack/fs_communicator.rb +109 -0
  16. data/lib/ruby-fs-stack/fs_utils.rb +27 -0
  17. data/lib/ruby-fs-stack/identity.rb +45 -0
  18. data/lib/ruby-fs-stack/warning_suppressor.rb +18 -0
  19. data/lib/ruby-fs-stack.rb +2 -0
  20. data/spec/communicator_spec.rb +214 -0
  21. data/spec/familytree_v2/familytree_communicator_spec.rb +309 -0
  22. data/spec/familytree_v2/json/match_KW3B-NNM.js +1 -0
  23. data/spec/familytree_v2/json/person/KJ86-3VD_all.js +1 -0
  24. data/spec/familytree_v2/json/person/KJ86-3VD_version.js +1 -0
  25. data/spec/familytree_v2/json/person/post_response.js +1 -0
  26. data/spec/familytree_v2/json/person/relationship_not_found.js +1 -0
  27. data/spec/familytree_v2/json/person/relationship_read.js +1 -0
  28. data/spec/familytree_v2/json/person/relationship_update.js +1 -0
  29. data/spec/familytree_v2/json/search.js +1 -0
  30. data/spec/familytree_v2/person_spec.rb +563 -0
  31. data/spec/familytree_v2/search_results_spec.rb +131 -0
  32. data/spec/fs_utils_spec.rb +33 -0
  33. data/spec/identity_v1/identity_spec.rb +50 -0
  34. data/spec/identity_v1/json/login.js +1 -0
  35. data/spec/ruby-fs-stack_spec.rb +6 -0
  36. data/spec/spec_helper.rb +27 -0
  37. 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,2 @@
1
+ require 'ruby-fs-stack/identity'
2
+ require 'ruby-fs-stack/familytree'
@@ -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