xoopit-cloud_query 0.1.5

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 nb.io
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,80 @@
1
+ cloudquery
2
+ ==========
3
+
4
+ Client for Xoopit's cloudquery API
5
+
6
+ Install
7
+ -------
8
+
9
+ Depends on `json`, `rack`, and `taf2-curb`. The install below should take
10
+ care of it. If not, `sudo gem install json rack taf2-curb` will do it.
11
+
12
+ Be sure you've run `gem sources -a http://gems.github.com` once on your system. Then:
13
+
14
+ sudo gem install xoopit-cloudquery
15
+
16
+ Simple contacts application example
17
+ -----------------------------------
18
+
19
+ > require 'rubygems'
20
+ => true
21
+ > require 'cloudquery'
22
+ => true
23
+ > include Cloudquery
24
+ => Object
25
+ > secret = Client.get_secret(<account_name>, <password>)
26
+ => "your secret appears here"
27
+ > c = Client.new(:account => '<account_name>', :secret => secret)
28
+ => #<Cloudquery::Client:0x10b1b24 @secure=true, @secret="your secret appears here", @account="<account_name>", @document_id_method=nil>
29
+ > c.add_indexes('superheroes')
30
+ => {"result"=>["kMzzzybpqpY"], "size"=>1, "STATUS"=>200}
31
+ > c.add_schema(File.open('simple.contact.xml'))
32
+ => {"result"=>["ubKme0EX3H2ud7VhBU7qngk3........."], "size"=>1, "STATUS"=>201}
33
+ > doc = {
34
+ 'simple.contact.name' => 'Steve Rogers',
35
+ 'simple.contact.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
36
+ 'simple.contact.telephone' => ['555-555-5555','123-456-6789'],
37
+ 'simple.contact.address' => ['Lower East Side, NY NY'],
38
+ 'simple.contact.birthday' => Date.parse('July 4, 1917'),
39
+ 'simple.contact.note' => 'Captain America!',
40
+ }
41
+ => {"simple.contact.birthday"=>#<Date: 4842827/2,0,2299161>, "simple.contact.address"=>["Lower East Side, NY NY"], "simple.contact.telephone"=>["555-555-5555", "123-456-6789"], "simple.contact.note"=>"Captain America!", "simple.contact.email"=>["steve.rogers@example.com", "captain.america@marvel.com"], "simple.contact.name"=>"Steve Rogers"}
42
+ > c.add_documents('superheroes', doc, 'simple.contact')
43
+ => {"result"=>["nDLCNLPo3oHtxANzG4YBn5kMzzzybpqpY"], "size"=>1, "STATUS"=>201}
44
+ > docs = [
45
+ {
46
+ 'simple.contact.name' => 'Clark Kent',
47
+ 'simple.contact.email' => ['clark.kent@example.com','superman@dc.com'],
48
+ 'simple.contact.telephone' => ['555-123-1234','555-456-6789'],
49
+ 'simple.contact.address' => ['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
50
+ 'simple.contact.birthday' => Date.parse('June 18, 1938'),
51
+ 'simple.contact.note' => 'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.'
52
+ },
53
+ {
54
+ 'simple.contact.name' => 'Bruce Wayne',
55
+ 'simple.contact.email' => ['bruce.wayne@example.com','batman@dc.com'],
56
+ 'simple.contact.telephone' => ['555-123-6666','555-456-6666'],
57
+ 'simple.contact.address' => ['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
58
+ 'simple.contact.birthday' => Date.parse('February 19, 1939'),
59
+ 'simple.contact.note' => 'Sidekick is Robin. Has problems with the Joker. Member of e justice league.'
60
+ }
61
+ ]
62
+ > c.add_documents('superheroes', docs, 'simple.contact')
63
+ => {"result"=>["lQgByVSvJk1skHtKpMYX40kMzzzybpqpY", "weJF4uDPJrlvrETTJQNibFkMzzzybpqpY"], "size"=>2, "STATUS"=>201}
64
+ > c.count_documents('superheroes', '*', 'simple.contact')
65
+ => {"result"=>3, "matches"=>3, "STATUS"=>200}
66
+ > c.get_documents('superheroes', '*', {:fields => 'simple.contact.name'}, 'simple.contact')
67
+ => {"result"=>[{"simple.contact.name"=>"Steve Rogers"}, {"simple.contact.name"=>"Clark Kent"}, {"simple.contact.name"=>"Bruce Wayne"}], "matches"=>3, "size"=>3, "STATUS"=>200}
68
+ > c.get_documents('superheroes', 'name:Steve', {:fields => 'simple.contact.name'}, 'simple.contact')
69
+ => {"result"=>[{"simple.contact.name"=>"Steve Rogers"}], "matches"=>1, "size"=>1, "STATUS"=>200}
70
+ > c.get_documents('superheroes', ':@:justice', {:fields => 'simple.contact.name'}, 'simple.contact')
71
+ => {"result"=>[{"simple.contact.name"=>"Clark Kent"}, {"simple.contact.name"=>"Bruce Wayne"}], "matches"=>2, "size"=>2, "STATUS"=>200}
72
+ > c.modify_documents('superheroes', 'name:steve', {'simple.contact.note' => 'His name is STEVE!'}, 'simple.contact')
73
+ => {"result"=>["nDLCNLPo3oHtxANzG4YBn5kMzzzybpqpY"], "matches"=>1, "size"=>1, "STATUS"=>200}
74
+ > c.delete_documents('superheroes', 'name:steve', 'simple.contact') => {"result"=>["nDLCNLPo3oHtxANzG4YBn5kMzzzybpqpY"], "matches"=>2, "size"=>1, "STATUS"=>200}
75
+
76
+
77
+ Copyright
78
+ ---------
79
+
80
+ Copyright (c) 2009 nb.io, LLC and Xoopit, Inc. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "cloud_query"
7
+ gem.summary = "Client for Xoopit's CloudQuery API"
8
+ gem.email = "us@nb.io"
9
+ gem.homepage = "http://github.com/xoopit/cloudquery_ruby"
10
+ gem.description = "Client for Xoopit's CloudQuery API"
11
+ gem.authors = ["Cameron Walters", "nb.io"]
12
+ gem.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
13
+ # gem.rubyforge_project = "cloudquery"
14
+ gem.add_dependency('rack', ">= 1.0")
15
+ gem.add_dependency('json', ">= 1.1.4")
16
+ gem.add_dependency('taf2-curb', ">= 0.2.8.0")
17
+
18
+
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ spec.spec_opts << "-c"
30
+ end
31
+
32
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
+ spec.libs << 'lib' << 'spec'
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ if File.exist?('VERSION.yml')
44
+ config = YAML.load(File.read('VERSION.yml'))
45
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
46
+ else
47
+ version = ""
48
+ end
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "CloudQuery #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
55
+
56
+ # begin
57
+ # require 'rake/contrib/sshpublisher'
58
+ # namespace :rubyforge do
59
+ #
60
+ # desc "Release gem and RDoc documentation to RubyForge"
61
+ # task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
62
+ #
63
+ # namespace :release do
64
+ # desc "Publish RDoc to RubyForge."
65
+ # task :docs => [:rdoc] do
66
+ # config = YAML.load(
67
+ # File.read(File.expand_path('~/.rubyforge/user-config.yml'))
68
+ # )
69
+ #
70
+ # host = "#{config['username']}@rubyforge.org"
71
+ # remote_dir = "/var/www/gforge-projects/cloudquery/"
72
+ # local_dir = 'rdoc'
73
+ #
74
+ # Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
75
+ # end
76
+ # end
77
+ # end
78
+ # rescue LoadError
79
+ # puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
80
+ # end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 5
@@ -0,0 +1,43 @@
1
+ require "rubygems"
2
+ require "uri"
3
+ require "digest/sha1"
4
+ require "base64"
5
+ require "rack/utils"
6
+ require "curl"
7
+ require "json"
8
+ require 'nokogiri'
9
+
10
+ require 'cloud_query/client'
11
+ require 'cloud_query/crypto'
12
+ require 'cloud_query/field'
13
+ require 'cloud_query/request'
14
+ require 'cloud_query/schema'
15
+
16
+ module CloudQuery
17
+ SCHEME = "https".freeze
18
+ HOST = "api.xoopit.com".freeze
19
+ PATH = "/v0".freeze
20
+
21
+ API_PATHS = {
22
+ :account => "account".freeze,
23
+ :schema => "schema".freeze,
24
+ :indexes => "i".freeze,
25
+ :documents => "i".freeze,
26
+ }.freeze
27
+
28
+ # Standard Content-Types for requests
29
+ CONTENT_TYPES = {
30
+ :json => 'application/json;charset=utf-8'.freeze,
31
+ :form => 'application/x-www-form-urlencoded'.freeze,
32
+ :xml => 'application/xml;charset=utf-8'.freeze,
33
+ }.freeze
34
+
35
+ SIGNING_METHOD = "SHA1".freeze
36
+ COOKIE_JAR = (ENV["COOKIE_JAR"] || ".cookies.lwp").freeze
37
+ end
38
+
39
+ class Time
40
+ def to_i_with_milliseconds
41
+ (to_f * 1000).to_i
42
+ end
43
+ end
data/lib/cloudquery.rb ADDED
@@ -0,0 +1 @@
1
+ require 'cloud_query'
@@ -0,0 +1,437 @@
1
+ require 'spec_helper'
2
+
3
+ if ENV["TEST_REAL_HTTP"]
4
+ # Create a config.yml file containing the following:
5
+ # :account: <your account name>
6
+ # :secret: <your secret>
7
+ # then run the specs with TEST_REAL_HTTP=true
8
+ describe "CloudQuery account" do
9
+ before(:each) do
10
+ @config = YAML.load(File.read('config.yml'))
11
+ @client = CloudQuery::Client.new(@config)
12
+ end
13
+
14
+ it "gets your account information from the server" do
15
+ response = @client.get_account
16
+ response['STATUS'].should be_between(200, 299)
17
+
18
+ account = response["result"]
19
+ account["secret"].should == @config[:secret]
20
+
21
+ account.should have_key("name")
22
+ account["name"].should == @config[:account]
23
+
24
+ account.should have_key("preferences")
25
+ end
26
+
27
+ it "updates your account on the server" do
28
+ account = @client.get_account["result"]
29
+ response = @client.update_account(account)
30
+ response['STATUS'].should be_between(200, 299)
31
+ end
32
+
33
+ it "adds a schema to your account on the server" do
34
+ response = @client.add_schema(File.open('spec/example_schema.xml'))
35
+ response['STATUS'].should be_between(200, 299)
36
+ end
37
+
38
+ it "gets the schemas for your account from the server" do
39
+ response = @client.get_schemas
40
+ response['STATUS'].should be_between(200, 299)
41
+ response['result'].should be_an_instance_of(Array)
42
+ response['result'].should have_at_least(1).item
43
+ end
44
+
45
+ it "deletes a schema from your account on the server" do
46
+ response = @client.delete_schema("spec.example")
47
+ response['STATUS'].should be_between(200, 299)
48
+ end
49
+
50
+ it "adds a single index to your account on the server" do
51
+ response = @client.add_indexes('spec_index')
52
+ response['STATUS'].should be_between(200, 299)
53
+ response['result'].should be_an_instance_of(Array)
54
+ response['result'].should have(1).item
55
+ end
56
+
57
+ it "adds multiple indexes to your account on the server" do
58
+ response = @client.add_indexes %w( spec_index_1 spec_index_2 spec_index_3 )
59
+ response['STATUS'].should be_between(200, 299)
60
+ response['result'].should be_an_instance_of(Array)
61
+ response['result'].should have(3).items
62
+ end
63
+
64
+ it "gets the indexes for your account from the server" do
65
+ response = @client.get_indexes
66
+ response['STATUS'].should be_between(200, 299)
67
+ response['result'].should be_an_instance_of(Array)
68
+ response['result'].should have_at_least(4).items
69
+ end
70
+
71
+ it "deletes a single index from your account on the server" do
72
+ response = @client.delete_indexes('spec_index')
73
+ response['STATUS'].should be_between(200, 299)
74
+ response['result'].should be_an_instance_of(Array)
75
+ response['result'].should have(1).item
76
+ end
77
+
78
+ it "deletes multiple indexes from your account on the server" do
79
+ response = @client.delete_indexes %w( spec_index_1 spec_index_2 spec_index_3 )
80
+ response['STATUS'].should be_between(200, 299)
81
+ response['result'].should be_an_instance_of(Array)
82
+ response['result'].should have(3).items
83
+ end
84
+
85
+ describe "document support" do
86
+ def valid_document
87
+ {
88
+ 'spec.example.name' => 'Steve Rogers',
89
+ 'spec.example.email' => ['steve.rogers@example.com','captain.america@marvel.com'],
90
+ 'spec.example.telephone' => ['555-555-5555','123-456-6789'],
91
+ 'spec.example.address' => ['Lower East Side, NY NY'],
92
+ 'spec.example.birthday' => ParseDate.parsedate('July 4, 1917'),
93
+ 'spec.example.note' => 'Captain America!',
94
+ }
95
+ end
96
+
97
+ def add_valid_document(index=nil)
98
+ index ||= 'spec_index'
99
+ response = @client.add_documents(index, valid_document, 'spec.example')
100
+ response['result'].first
101
+ end
102
+
103
+ before(:each) do
104
+ @client.add_indexes('spec_index')
105
+ @client.add_schema(File.open('spec/example_schema.xml'))
106
+ end
107
+
108
+ after(:each) do
109
+ @client.delete_schema("spec.example")
110
+ @client.delete_indexes('spec_index')
111
+ end
112
+
113
+ it "adds a document to an index on the server" do
114
+ response = @client.add_documents('spec_index', valid_document, 'spec.example')
115
+ response['STATUS'].should == 201
116
+ response['result'].should have(1).item
117
+ end
118
+
119
+ it "adds multiple documents to an index on the server" do
120
+ documents = [
121
+ valid_document,
122
+ {
123
+ 'spec.example.name' => 'Clark Kent',
124
+ 'spec.example.email' => ['clark.kent@example.com','superman@dc.com'],
125
+ 'spec.example.telephone' => ['555-123-1234', '555-456-6789'],
126
+ 'spec.example.address' =>
127
+ ['344 Clinton St., Apt. #3B, Metropolis', 'The Fortess of Solitude, North Pole'],
128
+ 'spec.example.birthday' => ParseDate.parsedate('June 18, 1938'),
129
+ 'spec.example.note' =>
130
+ 'Superhuman strength, speed, stamina, durability, senses, intelligence, regeneration, and longevity; super breath, heat vision, x-ray vision and flight. Member of the justice league.',
131
+ },
132
+ {
133
+ 'spec.example.name' => 'Bruce Wayne',
134
+ 'spec.example.email' => ['bruce.wayne@example.com','batman@dc.com'],
135
+ 'spec.example.telephone' => ['555-123-6666', '555-456-6666'],
136
+ 'spec.example.address' =>
137
+ ['1007 Mountain Drive, Gotham', 'The Batcave, Gotham'],
138
+ 'spec.example.birthday' => ParseDate.parsedate('February 19, 1939'),
139
+ 'spec.example.note' =>
140
+ 'Sidekick is Robin. Has problems with the Joker. Member of the justice league.',
141
+ },
142
+ ]
143
+
144
+ response = @client.add_documents('spec_index', documents, 'spec.example')
145
+ response['STATUS'].should == 201
146
+ response['result'].should have(3).items
147
+ end
148
+
149
+ it "updates a document on the server" do
150
+ doc = valid_document
151
+ doc['#.#'] = add_valid_document
152
+ doc['spec.example.note'] = "Document modified!"
153
+
154
+ response = @client.update_documents('spec_index', doc, 'spec.example')
155
+ response['STATUS'].should == 200
156
+ response['result'].should have(1).item
157
+ end
158
+
159
+ it "modifies documents on the server" do
160
+ add_valid_document
161
+ mods = {'spec.example.note' => 'Document modified!'}
162
+ response = @client.modify_documents(
163
+ "spec_index",
164
+ "name:#{valid_document['spec.example.name']}",
165
+ mods,
166
+ "spec.example"
167
+ )
168
+ response['STATUS'].should == 200 # OK
169
+ response['result'].should have(1).item
170
+ end
171
+
172
+ it "gets a document from the server" do
173
+ add_valid_document
174
+ response = @client.get_documents('spec_index', nil, {}, 'spec.example')
175
+ response['STATUS'].should == 200
176
+ response['result'].should have(1).item
177
+ stored_document = response['result'].first
178
+ valid_document.each { |key, value| stored_document.should have_key(key) }
179
+ end
180
+
181
+ it "gets a document from multiple indexes on the server" do
182
+ @client.add_indexes('spec_index_2')
183
+ @client.delete_documents(nil, nil)
184
+ add_valid_document
185
+ add_valid_document('spec_index_2')
186
+
187
+ response = @client.get_documents(nil, nil, {}, 'spec.example')
188
+ response['STATUS'].should == 200
189
+ response['result'].should have(2).items
190
+ stored_document_1 = response['result'].first
191
+ stored_document_2 = response['result'].last
192
+
193
+ valid_document.each { |key, value| stored_document_1.should have_key(key) }
194
+ valid_document.each { |key, value| stored_document_2.should have_key(key) }
195
+
196
+ @client.delete_indexes('spec_index_2')
197
+ end
198
+
199
+ it "counts documents from the server" do
200
+ @client.delete_documents(nil, nil)
201
+ add_valid_document
202
+
203
+ response = @client.count_documents('spec_index', '*', 'spec.example')
204
+ response['STATUS'].should == 200
205
+ response['result'].should == 1
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ describe CloudQuery::Client do
212
+ before(:each) do
213
+ @valid_options = {
214
+ :account => 'account',
215
+ :secret => 'secret'
216
+ }
217
+ end
218
+
219
+ def client(options={})
220
+ return @client if defined?(@client)
221
+ @client = CloudQuery::Client.new(@valid_options.merge(options))
222
+ @client.stub!(:execute_request)
223
+ @client
224
+ end
225
+
226
+ it "instantiates when passed valid arguments" do
227
+ lambda { client }.should_not raise_error
228
+ end
229
+
230
+ end
231
+
232
+ describe CloudQuery::Request do
233
+ before(:each) do
234
+ @valid_options = {
235
+ :scheme => 'http',
236
+ :host => 'example.com',
237
+ :path => '/super/duper/path',
238
+ }
239
+ end
240
+
241
+ def request(additional_options={})
242
+ return @request if defined?(@request)
243
+ @request = CloudQuery::Request.new(@valid_options.merge(additional_options))
244
+ end
245
+
246
+ it "instantiates with valid options" do
247
+ lambda { request }.should_not raise_error
248
+ end
249
+
250
+ describe "request_uri" do
251
+ describe "without an account or secret" do
252
+ it "appends the query_str to the path after '?'" do
253
+ request.should_receive(:query_str).at_least(:once).and_return("query=string&more=params")
254
+ request.request_uri.should == "#{request.path}?#{request.send(:query_str)}"
255
+ end
256
+
257
+ it "doesn't append a '?' when query_str is empty" do
258
+ request.should_receive(:query_str).at_least(:once).and_return("")
259
+ request.request_uri.should == request.path
260
+ request.request_uri.should_not equal(request.path) #ensure we don't accidentally modify request's instance variable
261
+ end
262
+ end
263
+
264
+ describe "with an account" do
265
+ it "should append the signature_params" do
266
+ params = request(:account => 'account').request_uri.sub(/^[^?]+\?/, '').split('&')
267
+ params.select { |n| n.match(/^x_/) }.should have(4).items
268
+ end
269
+
270
+ describe "and a secret" do
271
+ it "should append the signature when the secret is provided" do
272
+ params = request(:account => 'account', :secret => 'secret').request_uri.sub(/^[^?]+\?/, '').split('&')
273
+ x_params = params.select { |n| n.match(/^x_/) }
274
+ x_params.should have(5).items
275
+ x_params.last.should match(/^x_sig=[0-9a-zA-Z\-._%]+/)
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ describe "url" do
282
+ it "constructs a full URL from the scheme, host, and request_uri" do
283
+ request.url.should ==
284
+ "#{request.scheme}://#{request.host}#{request.request_uri}"
285
+ end
286
+
287
+ it "constructs a url using a port override" do
288
+ request(:port => 8080).url.should ==
289
+ "#{request.scheme}://#{request.host}:8080#{request.request_uri}"
290
+ end
291
+
292
+ it "constructs a url using a path override" do
293
+ request(:path => '/another/path').url.should ==
294
+ "#{request.scheme}://#{request.host}#{request.request_uri}"
295
+ end
296
+
297
+ it "constructs a url with default query parameters" do
298
+ request(:params => {'these' => 'params'}).url.should ==
299
+ "#{request.scheme}://#{request.host}#{request.request_uri}"
300
+ request.url.should match(/these=params$/)
301
+ end
302
+
303
+ describe "without an account or secret" do
304
+ it "does not append the x_<params>" do
305
+ request.url.should_not match(/x_/)
306
+ end
307
+ end
308
+
309
+ describe "with an account" do
310
+ it "appends the signature params" do
311
+ url = request(:account => 'account').url
312
+ query = Rack::Utils.parse_query(url.split('?').last)
313
+ request.send(:signature_params).keys.each do |param_name|
314
+ query.should have_key(param_name)
315
+ end
316
+ end
317
+
318
+ describe "and a secret" do
319
+ it "appends the signature params and x_sig with the signature" do
320
+ url = request(:account => 'account', :secret => 'secret').url
321
+ query = Rack::Utils.parse_query(url.split('?').last)
322
+ signature_params = request.send(:signature_params).keys
323
+ signature_params.each do |param_name|
324
+ query.should have_key(param_name)
325
+ end
326
+ query.should have_key('x_sig')
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+ describe "private methods" do
333
+
334
+ describe "append_signature" do
335
+ it "should append the signature as the x_sig parameter at the end of the query string" do
336
+ url = 'http://example.com/path?query=string'
337
+ signed_url = request.send(:append_signature, url, 'secret')
338
+ signed_url.should match(/^#{url.sub(/\?/, '\\?')}/)
339
+ signed_url.should match(/x_sig=[-\w]+(?:%3D)*$/)
340
+ end
341
+ end
342
+
343
+ describe "signature_params" do
344
+ describe "without an account present" do
345
+ it "should return an empty hash" do
346
+ request.send(:signature_params).should == {}
347
+ end
348
+ end
349
+
350
+ describe "with an account present" do
351
+ before(:each) do
352
+ @params = request(:account => 'account').send(:signature_params)
353
+ end
354
+
355
+ it "should return a hash with the x_name parameter with the account name" do
356
+ @params.should have_key('x_name')
357
+ @params['x_name'].should == 'account'
358
+ end
359
+
360
+ it "should return a hash with the x_time parameter with the current milliseconds since epoch" do
361
+ @params.should have_key('x_time')
362
+ @params['x_time'].should be_close(Time.now.to_i_with_milliseconds, 100)
363
+ end
364
+
365
+ it "should return a hash with the x_nonce parameter of the format \d+.\d+" do
366
+ @params.should have_key('x_nonce')
367
+ @params['x_nonce'].should match(/^\d+.\d+$/)
368
+ end
369
+
370
+ it "should return a hash with the x_method parameter with the signing method name" do
371
+ @params.should have_key('x_method')
372
+ @params['x_method'].should == CloudQuery::SIGNING_METHOD
373
+ end
374
+ end
375
+ end
376
+
377
+ describe "query_str" do
378
+ it "builds a query string from the request params" do
379
+ request(:params => {'these' => 'params'})
380
+ request.send(:query_str).should == 'these=params'
381
+ end
382
+
383
+ it "url-encodes params with non alphanumeric characters (outside [ a-zA-Z0-9-._])" do
384
+ request(:params => {'weird' => 'values=here'})
385
+ request.send(:query_str).should == 'weird=values%3Dhere'
386
+ end
387
+
388
+ it "returns an empty string when no params are present" do
389
+ request(:params => {}).send(:query_str) == ""
390
+ end
391
+ end
392
+
393
+ describe "base_uri" do
394
+ it "returns an http url when the scheme is http" do
395
+ request(:scheme => 'http').send(:base_uri).should be_an_instance_of(URI::HTTP)
396
+ end
397
+ it "returns an https url when the scheme is https" do
398
+ request(:scheme => 'https').send(:base_uri).should be_an_instance_of(URI::HTTPS)
399
+ end
400
+ end
401
+ end
402
+
403
+ end
404
+
405
+ describe CloudQuery::Crypto::Random do
406
+ describe "nonce generation" do
407
+ it "generates a nonce with a random number, a dot, and the current time" do
408
+ nonce = CloudQuery::Crypto::Random.nonce
409
+ nonce.should match(/^\d+.\d+$/)
410
+ random_digits, time = nonce.split('.')
411
+ time.to_i.should be_close(Time.now.to_i, 1)
412
+ random_digits.should match(/^\d+$/)
413
+ end
414
+ end
415
+ end
416
+
417
+ describe CloudQuery::Crypto::URLSafeSHA1 do
418
+ describe "sign" do
419
+ it "takes an arbitrary number of tokens to encrypt" do
420
+ lambda { CloudQuery::Crypto::URLSafeSHA1.sign }.should_not raise_error
421
+ lambda { CloudQuery::Crypto::URLSafeSHA1.sign('a') }.should_not raise_error
422
+ lambda { CloudQuery::Crypto::URLSafeSHA1.sign('a', 'b', 'c') }.should_not raise_error
423
+ end
424
+
425
+ it "produces a url-safe base64 encoded SHA1 digest of tokens" do
426
+ 20.times do
427
+ token = CloudQuery::Crypto::Random.nonce
428
+ signature = CloudQuery::Crypto::URLSafeSHA1.sign(token)
429
+ signature.should_not include('+')
430
+ signature.should_not include('/')
431
+
432
+ b64_digest = Base64.encode64(Digest::SHA1.digest(token)).chomp.tr('+/', '-_')
433
+ signature.should == b64_digest
434
+ end
435
+ end
436
+ end
437
+ end
@@ -0,0 +1,26 @@
1
+ <schema name="spec.example" store="yes">
2
+ <!-- The full name of the contact -->
3
+ <field name="name"
4
+ type="string"
5
+ analyzer="LCWhitespaceAnalyzer"
6
+ usage="user" />
7
+ <!-- The email addresses. A json array: email address -->
8
+ <field name="email"
9
+ type="string"
10
+ usage="user" />
11
+ <!-- The phone numbers. A json array: phone number -->
12
+ <field name="telephone"
13
+ type="string"
14
+ usage="user" />
15
+ <!-- The addresses. A json array: address -->
16
+ <field name="address"
17
+ type="string"
18
+ usage="user" />
19
+ <!-- The birthday of the contact-->
20
+ <field name="birthday"
21
+ type="date" />
22
+ <!-- A note for the contact-->
23
+ <field name="note"
24
+ type="text"
25
+ usage="user" />
26
+ </schema>
@@ -0,0 +1,11 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'cloud_query'
6
+ require 'parsedate'
7
+ require 'pp'
8
+
9
+ Spec::Runner.configure do |config|
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xoopit-cloud_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Walters
8
+ - nb.io
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-05-04 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rack
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "1.0"
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: json
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 1.1.4
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: taf2-curb
38
+ type: :runtime
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 0.2.8.0
45
+ version:
46
+ description: Client for Xoopit's CloudQuery API
47
+ email: us@nb.io
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - LICENSE
54
+ - README.markdown
55
+ files:
56
+ - LICENSE
57
+ - README.markdown
58
+ - Rakefile
59
+ - VERSION.yml
60
+ - lib/cloudquery.rb
61
+ - lib/cloud_query.rb
62
+ - spec/cloudquery_spec.rb
63
+ - spec/example_schema.xml
64
+ - spec/spec_helper.rb
65
+ has_rdoc: true
66
+ homepage: http://github.com/nbio/cloudquery
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --charset=UTF-8
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.2.0
88
+ signing_key:
89
+ specification_version: 2
90
+ summary: Client for Xoopit's cloudquery API
91
+ test_files:
92
+ - spec/cloudquery_spec.rb
93
+ - spec/spec_helper.rb