appengine-apis 0.0.8 → 0.0.9
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/Manifest.txt +5 -0
- data/README.rdoc +2 -0
- data/lib/appengine-apis.rb +1 -1
- data/lib/appengine-apis/datastore.rb +41 -14
- data/lib/appengine-apis/datastore_types.rb +194 -5
- data/lib/appengine-apis/labs/taskqueue.rb +266 -0
- data/lib/appengine-apis/tempfile.rb +52 -0
- data/lib/appengine-apis/urlfetch.rb +4 -0
- data/lib/appengine-apis/xmpp.rb +283 -0
- data/spec/datastore_spec.rb +55 -1
- data/spec/datastore_types_spec.rb +125 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/taskqueue_spec.rb +110 -0
- data/spec/urlfetch_spec.rb +5 -0
- data/spec/xmpp_spec.rb +223 -0
- metadata +9 -2
@@ -0,0 +1,52 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
# Copyright:: Copyright 2009 Google Inc.
|
3
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
# Replace TempFile with a StringIO.
|
18
|
+
|
19
|
+
$" << "tempfile.rb"
|
20
|
+
|
21
|
+
require 'stringio'
|
22
|
+
|
23
|
+
TempFile = Class.new(StringIO)
|
24
|
+
|
25
|
+
class Tempfile < StringIO
|
26
|
+
attr_reader :path
|
27
|
+
|
28
|
+
def initialize(basename, tmpdir=nil)
|
29
|
+
@path = basename
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
|
33
|
+
def unlink; end
|
34
|
+
|
35
|
+
def close(*args)
|
36
|
+
super()
|
37
|
+
end
|
38
|
+
|
39
|
+
def open; end
|
40
|
+
|
41
|
+
alias close! close
|
42
|
+
alias delete unlink
|
43
|
+
alias length size
|
44
|
+
|
45
|
+
def self.open(*args)
|
46
|
+
if block_given?
|
47
|
+
yield new(*args)
|
48
|
+
else
|
49
|
+
new(*args)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -69,6 +69,7 @@ module AppEngine
|
|
69
69
|
# redirect chaininformation. If false, you see the HTTP response
|
70
70
|
# yourself, including the 'Location' header, and redirects are not
|
71
71
|
# followed.
|
72
|
+
# [:deadline] Deadline, in seconds, for the request.
|
72
73
|
#
|
73
74
|
# Returns a Net::HTTPResponse.
|
74
75
|
#
|
@@ -103,6 +104,7 @@ module AppEngine
|
|
103
104
|
headers = options.delete(:headers) || {}
|
104
105
|
truncate = options.delete(:allow_truncated)
|
105
106
|
follow_redirects = options.delete(:follow_redirects) || true
|
107
|
+
deadline = options.delete(:deadline)
|
106
108
|
|
107
109
|
unless options.empty?
|
108
110
|
raise ArgumentError, "Unsupported options #{options.inspect}."
|
@@ -125,6 +127,8 @@ module AppEngine
|
|
125
127
|
options.do_not_follow_redirects
|
126
128
|
end
|
127
129
|
|
130
|
+
options.set_deadline(deadline) if deadline
|
131
|
+
|
128
132
|
url = java.net.URL.new(url) unless url.java_kind_of? java.net.URL
|
129
133
|
request = HTTPRequest.new(url, method, options)
|
130
134
|
|
@@ -0,0 +1,283 @@
|
|
1
|
+
#!/usr/bin/ruby1.8 -w
|
2
|
+
#
|
3
|
+
# Copyright:: Copyright 2009 Google Inc.
|
4
|
+
# Original Author:: Ryan Brown (mailto:ribrdb@google.com)
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
# XMPP API.
|
19
|
+
|
20
|
+
require 'appengine-apis/apiproxy'
|
21
|
+
require 'logger'
|
22
|
+
|
23
|
+
module AppEngine
|
24
|
+
|
25
|
+
# The XMPP api provides an interface for accessing XMPP status information,
|
26
|
+
# sending XMPP messages, and parsing XMPP responses.
|
27
|
+
module XMPP
|
28
|
+
module Proto
|
29
|
+
%w(PresenceRequest PresenceResponse
|
30
|
+
XmppInviteRequest XmppInviteResponse
|
31
|
+
XmppMessageRequest XmppMessageResponse
|
32
|
+
XmppServiceError).each do |name|
|
33
|
+
const_set(name, JavaUtilities.get_proxy_or_package_under_package(
|
34
|
+
com.google.appengine.api.xmpp, "XMPPServicePb$#{name}"
|
35
|
+
))
|
36
|
+
end
|
37
|
+
ErrorCode = XmppServiceError::ErrorCode
|
38
|
+
end
|
39
|
+
|
40
|
+
class XMPPError < StandardError; end
|
41
|
+
|
42
|
+
module Status
|
43
|
+
NO_ERROR = Proto::XmppMessageResponse::XmppMessageStatus::NO_ERROR.value
|
44
|
+
INVALID_JID =
|
45
|
+
Proto::XmppMessageResponse::XmppMessageStatus::INVALID_JID.value
|
46
|
+
OTHER_ERROR =
|
47
|
+
Proto::XmppMessageResponse::XmppMessageStatus::OTHER_ERROR.value
|
48
|
+
end
|
49
|
+
|
50
|
+
# Represents presence information returned by the server.
|
51
|
+
class Presence
|
52
|
+
def initialize(available)
|
53
|
+
@available = available
|
54
|
+
end
|
55
|
+
|
56
|
+
def available?
|
57
|
+
@available
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Represents an incoming or outgoing XMPP Message.
|
62
|
+
# Also includes support for parsing chat commands. Commands are of the form
|
63
|
+
# /{command} {arg}?
|
64
|
+
# A backslash is also recognized as the first character to support chat
|
65
|
+
# client which internally handle / commands.
|
66
|
+
class Message
|
67
|
+
ARG_INDEX = {:to => 0, :body => 1, :from => 2, :type => 3, :xml => 4}
|
68
|
+
COMMAND_REGEX = /^[\\\/](\S+)(\s+(.+))?/
|
69
|
+
|
70
|
+
attr_reader :type, :sender, :recipients, :body
|
71
|
+
|
72
|
+
# call-seq:
|
73
|
+
# Message.new(to, body, from=nil, type=:chat, xml=false)
|
74
|
+
# or
|
75
|
+
# Message.new(options)
|
76
|
+
#
|
77
|
+
# Constructor for sending an outgoing XMPP message or parsing
|
78
|
+
# an incoming XMPP message.
|
79
|
+
#
|
80
|
+
# Args / Options:
|
81
|
+
# [:to] Destination JID or array of JIDs for the message.
|
82
|
+
# [:body] Body of the message.
|
83
|
+
# [:from]
|
84
|
+
# Optional custom sender JID. The default is <appid>@appspot.com.
|
85
|
+
# Custom JIDs can be of the form <anything>@<appid>.appspotchat.com.
|
86
|
+
# [:type]
|
87
|
+
# Optional type. Valid types are :chat, :error, :groupchat,
|
88
|
+
# :headline, and :normal. See RFC 3921, section 2.1.1. The default
|
89
|
+
# is :chat.
|
90
|
+
# [:xml]
|
91
|
+
# If true specifies that the body should be interpreted as XML.
|
92
|
+
# If false, the contents of the body will be escaped and placed
|
93
|
+
# inside of a body element inside of the message. If true, the
|
94
|
+
# contents will be made children of the message.
|
95
|
+
def initialize(*args)
|
96
|
+
if args.size == 1
|
97
|
+
options = args[0]
|
98
|
+
elsif args[-1].kind_of? Hash
|
99
|
+
options = args.pop
|
100
|
+
else
|
101
|
+
options = {}
|
102
|
+
end
|
103
|
+
@recipients = fetch_arg(:to, options, args)
|
104
|
+
@body = fetch_arg(:body, options, args)
|
105
|
+
unless @recipients && @body
|
106
|
+
raise ArgumentError, "Recipient and body are required."
|
107
|
+
end
|
108
|
+
@recipients = [@recipients] unless @recipients.kind_of? Array
|
109
|
+
|
110
|
+
@sender = fetch_arg(:from, options, args)
|
111
|
+
@type = fetch_arg(:type, options, args) || :chat
|
112
|
+
@xml = !!fetch_arg(:xml, options, args)
|
113
|
+
end
|
114
|
+
|
115
|
+
def xml?
|
116
|
+
@xml
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns the command if this message contains a chat command.
|
120
|
+
def command
|
121
|
+
parse_command
|
122
|
+
@command
|
123
|
+
end
|
124
|
+
|
125
|
+
# If this message contains a chat command, returns the command argument.
|
126
|
+
# Otherwise, returns the message body.
|
127
|
+
def arg
|
128
|
+
parse_command
|
129
|
+
@arg
|
130
|
+
end
|
131
|
+
|
132
|
+
# Convenience method to reply to a message.
|
133
|
+
def reply(body, type=:chat, xml=false)
|
134
|
+
message = Message.new([sender], body, recipients[0], type, xml)
|
135
|
+
XMPP.send_message(message)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def parse_command
|
140
|
+
return if @arg
|
141
|
+
if body =~ COMMAND_REGEX
|
142
|
+
@command = $1
|
143
|
+
@arg = $3 || ''
|
144
|
+
else
|
145
|
+
@arg = body
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_proto
|
150
|
+
proto = Proto::XmppMessageRequest.new
|
151
|
+
recipients.each do |jid|
|
152
|
+
proto.add_jid(jid)
|
153
|
+
end
|
154
|
+
proto.set_body(body)
|
155
|
+
proto.set_raw_xml(xml?)
|
156
|
+
proto.set_from_jid(sender) if sender
|
157
|
+
proto.set_type(type.to_s)
|
158
|
+
proto
|
159
|
+
end
|
160
|
+
|
161
|
+
def fetch_arg(name, options, args)
|
162
|
+
arg = options[name] || args[ARG_INDEX[name]]
|
163
|
+
unless arg.kind_of? String
|
164
|
+
if arg.respond_to? :read
|
165
|
+
arg = arg.read
|
166
|
+
elsif arg.kind_of? Hash
|
167
|
+
arg.each do |key, value|
|
168
|
+
if value.respond_to? :read
|
169
|
+
arg = value.read
|
170
|
+
break
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
arg
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class << self
|
180
|
+
|
181
|
+
# Get the presence for a JID.
|
182
|
+
#
|
183
|
+
# Args:
|
184
|
+
# - jid: The JID of the contact whose presence is requested.
|
185
|
+
# - from_jid: Optional custom sender JID.
|
186
|
+
# The default is <appid>@appspot.com. Custom JIDs can be of the form
|
187
|
+
# <anything>@<appid>.appspotchat.com.
|
188
|
+
#
|
189
|
+
# Returns:
|
190
|
+
# - A Presence object.
|
191
|
+
def get_presence(jid, from_jid=nil)
|
192
|
+
raise ArgumentError, 'Jabber ID cannot be nil' if jid.nil?
|
193
|
+
request = Proto::PresenceRequest.new
|
194
|
+
request.set_jid(jid)
|
195
|
+
request.set_from_jid(from_jid) if from_jid
|
196
|
+
|
197
|
+
response = make_sync_call('GetPresence', request,
|
198
|
+
Proto::PresenceResponse)
|
199
|
+
Presence.new(response.isIsAvailable)
|
200
|
+
rescue ApiProxy::ApplicationException => ex
|
201
|
+
case Proto::ErrorCode.value_of(ex.application_error)
|
202
|
+
when Proto::ErrorCode::INVALID_JID
|
203
|
+
raise ArgumentError, "Invalid jabber ID: #{jid}"
|
204
|
+
else
|
205
|
+
raise XMPPError, 'Unknown error retrieving presence for jabber ID: ' +
|
206
|
+
jid
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Send a chat invitaion.
|
211
|
+
#
|
212
|
+
# Args:
|
213
|
+
# - jid: JID of the contact to invite.
|
214
|
+
# - from_jid: Optional custom sender JID.
|
215
|
+
# The default is <appid>@appspot.com. Custom JIDs can be of the form
|
216
|
+
# <anything>@<appid>.appspotchat.com.
|
217
|
+
def send_invitation(jid, from_jid=nil)
|
218
|
+
raise ArgumentError, 'Jabber ID cannot be nil' if jid.nil?
|
219
|
+
request = Proto::XmppInviteRequest.new
|
220
|
+
request.set_jid(jid)
|
221
|
+
request.set_from_jid(from_jid) if from_jid
|
222
|
+
|
223
|
+
make_sync_call('SendInvite', request, Proto::XmppInviteResponse)
|
224
|
+
nil
|
225
|
+
rescue ApiProxy::ApplicationException => ex
|
226
|
+
case Proto::ErrorCode.value_of(ex.application_error)
|
227
|
+
when Proto::ErrorCode::INVALID_JID
|
228
|
+
raise ArgumentError, "Invalid jabber ID: #{jid}"
|
229
|
+
else
|
230
|
+
raise XMPPError, 'Unknown error sending invitation to jabber ID: ' +
|
231
|
+
jid
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# call-seq:
|
237
|
+
# XMPP.send_message(message)
|
238
|
+
# or
|
239
|
+
# XMPP.send_message(*message_args)
|
240
|
+
#
|
241
|
+
# Send a chat message.
|
242
|
+
#
|
243
|
+
# Args:
|
244
|
+
# - message: A Message object to send.
|
245
|
+
# - message_args: Used to create a new Message. See #Message.new
|
246
|
+
#
|
247
|
+
# Returns an Array Statuses, one for each JID, corresponding to the
|
248
|
+
# result of sending the message to that JID.
|
249
|
+
def send_message(*args)
|
250
|
+
if args[0].kind_of? Message
|
251
|
+
message = args[0]
|
252
|
+
else
|
253
|
+
message = Message.new(*args)
|
254
|
+
end
|
255
|
+
request = message.send :to_proto
|
256
|
+
response = make_sync_call('SendMessage', request,
|
257
|
+
Proto::XmppMessageResponse)
|
258
|
+
response.status_iterator.to_a
|
259
|
+
rescue ApiProxy::ApplicationException => ex
|
260
|
+
case Proto::ErrorCode.value_of(ex.application_error)
|
261
|
+
when Proto::ErrorCode::INVALID_JID
|
262
|
+
raise ArgumentError, "Invalid jabber ID"
|
263
|
+
when Proto::ErrorCode::NO_BODY
|
264
|
+
raise ArgumentError, "Missing message body"
|
265
|
+
when Proto::ErrorCode::INVALID_XML
|
266
|
+
raise ArgumentError, "Invalid XML body"
|
267
|
+
when Proto::ErrorCode::INVALID_TYPE
|
268
|
+
raise ArgumentError, "Invalid type #{message.type.inspect}"
|
269
|
+
else
|
270
|
+
raise XMPPError, 'Unknown error sending message'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
def make_sync_call(call, request, response_class)
|
276
|
+
bytes = ApiProxy.make_sync_call('xmpp', call, request.to_byte_array)
|
277
|
+
response = response_class.new
|
278
|
+
response.merge_from(bytes)
|
279
|
+
return response
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
data/spec/datastore_spec.rb
CHANGED
@@ -35,6 +35,14 @@ describe AppEngine::Datastore do
|
|
35
35
|
stored[:b].should == "b"
|
36
36
|
end
|
37
37
|
|
38
|
+
it 'should raise EntityNotFound' do
|
39
|
+
p = lambda do
|
40
|
+
key = Datastore::Key.from_path("Does", "not exist")
|
41
|
+
e = Datastore.get(key)
|
42
|
+
end
|
43
|
+
p.should raise_error Datastore::EntityNotFound
|
44
|
+
end
|
45
|
+
|
38
46
|
it "should support Text" do
|
39
47
|
entity = Datastore::Entity.new("Test")
|
40
48
|
entity[:a] = Datastore::Text.new("a")
|
@@ -73,6 +81,25 @@ describe AppEngine::Datastore do
|
|
73
81
|
p.should raise_error Datastore::TransactionFailed
|
74
82
|
end
|
75
83
|
|
84
|
+
it "should support query transactions" do
|
85
|
+
a = Datastore::Entity.new("A")
|
86
|
+
Datastore.put(a)
|
87
|
+
b = Datastore::Entity.new("B", a.key)
|
88
|
+
b[:a] = 0
|
89
|
+
Datastore.put(b)
|
90
|
+
p = lambda do
|
91
|
+
Datastore.transaction do
|
92
|
+
b2 = Datastore::Query.new("B", a.key).entity
|
93
|
+
b[:a] += 1
|
94
|
+
Datastore.put(nil, b)
|
95
|
+
Datastore.put(b2)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
pending("Local ancestory only queries") do
|
99
|
+
p.should raise_error Datastore::TransactionFailed
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
76
103
|
it "should retry transactions" do
|
77
104
|
a = Datastore::Entity.new("A")
|
78
105
|
a[:a] = 0
|
@@ -92,6 +119,29 @@ describe AppEngine::Datastore do
|
|
92
119
|
lambda {Datastore.transaction{ raise "Foo"}}.should raise_error "Foo"
|
93
120
|
Datastore.active_transactions.to_a.should == []
|
94
121
|
end
|
122
|
+
|
123
|
+
describe "extract_tx" do
|
124
|
+
it "should support a single key" do
|
125
|
+
key = Datastore::Key.from_path("A", 1)
|
126
|
+
Datastore.extract_tx([key]).should == [key]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should support allocate_ids" do
|
131
|
+
ids = Datastore.allocate_ids('Foo', 1)
|
132
|
+
ids.size.should == 1
|
133
|
+
ids.map{|x| x}.should == [ids.start]
|
134
|
+
ids.start.should == ids.end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should support allocate_ids with parent" do
|
138
|
+
parent = Datastore::Key.from_path("A", 1)
|
139
|
+
ids = Datastore.allocate_ids(parent, 'Foo', 2)
|
140
|
+
ids.size.should == 2
|
141
|
+
ids.start.id.should == ids.end.id - 1
|
142
|
+
ids.map {|x| x}.size.should == 2
|
143
|
+
ids.start.parent.should == parent
|
144
|
+
end
|
95
145
|
end
|
96
146
|
|
97
147
|
describe AppEngine::Datastore::Query do
|
@@ -159,5 +209,9 @@ describe AppEngine::Datastore::Query do
|
|
159
209
|
q.sort('name', Query::DESCENDING)
|
160
210
|
q.fetch.to_a.should == [@aa, @a]
|
161
211
|
end
|
162
|
-
|
212
|
+
|
213
|
+
it "should support count" do
|
214
|
+
q = Query.new("A")
|
215
|
+
q.count.should == 2
|
216
|
+
end
|
163
217
|
end
|
@@ -160,6 +160,57 @@ describe AppEngine::Datastore::Entity do
|
|
160
160
|
@entity['time'].class.should == Time
|
161
161
|
end
|
162
162
|
|
163
|
+
it "should support Email" do
|
164
|
+
email = "ribrdb@example.com"
|
165
|
+
@entity['email'] = AppEngine::Datastore::Email.new(email)
|
166
|
+
@entity['email'].should == email
|
167
|
+
@entity['email'].class.should == AppEngine::Datastore::Email
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should support Category" do
|
171
|
+
category = "food"
|
172
|
+
@entity['cat'] = AppEngine::Datastore::Category.new(category)
|
173
|
+
@entity['cat'].should == category
|
174
|
+
@entity['cat'].class.should == AppEngine::Datastore::Category
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should support PhoneNumbers" do
|
178
|
+
number = '555-1212'
|
179
|
+
@entity['phone'] = AppEngine::Datastore::PhoneNumber.new(number)
|
180
|
+
@entity['phone'].should == number
|
181
|
+
@entity['phone'].class.should == AppEngine::Datastore::PhoneNumber
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should support PostalAddress" do
|
185
|
+
address = '345 Spear St'
|
186
|
+
@entity['address'] = AppEngine::Datastore::PostalAddress.new(address)
|
187
|
+
@entity['address'].should == address
|
188
|
+
@entity['address'].class.should == AppEngine::Datastore::PostalAddress
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should support Rating" do
|
192
|
+
rating = 34
|
193
|
+
@entity['rating'] = AppEngine::Datastore::Rating.new(rating)
|
194
|
+
@entity['rating'].rating.should == rating
|
195
|
+
@entity['rating'].class.should == AppEngine::Datastore::Rating
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should support IMHandle" do
|
199
|
+
im = AppEngine::Datastore::IMHandle.new(:xmpp, 'batman@google.com')
|
200
|
+
@entity['im'] = im
|
201
|
+
@entity['im'].should == im
|
202
|
+
@entity['im'].class.should == AppEngine::Datastore::IMHandle
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should support GeoPt" do
|
206
|
+
latitude = 32.4
|
207
|
+
longitude = 72.2
|
208
|
+
@entity['address'] = AppEngine::Datastore::GeoPt.new(latitude, longitude)
|
209
|
+
@entity['address'].latitude.should be_close latitude, 0.001
|
210
|
+
@entity['address'].longitude.should be_close longitude, 0.001
|
211
|
+
@entity['address'].class.should == AppEngine::Datastore::GeoPt
|
212
|
+
end
|
213
|
+
|
163
214
|
it "should support multiple values" do
|
164
215
|
list = [1, 2, 3]
|
165
216
|
@entity['list'] = list
|
@@ -211,3 +262,77 @@ describe AppEngine::Datastore::Text do
|
|
211
262
|
end
|
212
263
|
end
|
213
264
|
|
265
|
+
describe AppEngine::Datastore::Rating do
|
266
|
+
it 'should support ==' do
|
267
|
+
a = AppEngine::Datastore::Rating.new(27)
|
268
|
+
b = AppEngine::Datastore::Rating.new(27)
|
269
|
+
a.should == b
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should support <=>' do
|
273
|
+
a = AppEngine::Datastore::Rating.new(3)
|
274
|
+
b = AppEngine::Datastore::Rating.new(4)
|
275
|
+
a.should be < b
|
276
|
+
b.should be > a
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'should check MIN_VALUE' do
|
280
|
+
l = lambda {AppEngine::Datastore::Rating.new -1}
|
281
|
+
l.should raise_error ArgumentError
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'should check MAX_VALUE' do
|
285
|
+
l = lambda {AppEngine::Datastore::Rating.new 101}
|
286
|
+
l.should raise_error ArgumentError
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe AppEngine::Datastore::GeoPt do
|
291
|
+
it 'should support ==' do
|
292
|
+
a = AppEngine::Datastore::GeoPt.new(35, 62)
|
293
|
+
b = AppEngine::Datastore::GeoPt.new(a.latitude, a.longitude)
|
294
|
+
a.should == b
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'should support <=>' do
|
298
|
+
a = AppEngine::Datastore::GeoPt.new(35, 62)
|
299
|
+
b = AppEngine::Datastore::GeoPt.new(36, 62)
|
300
|
+
a.should be < b
|
301
|
+
b.should be > a
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'should convert exceptions' do
|
305
|
+
l = lambda {AppEngine::Datastore::GeoPt.new(700, 999)}
|
306
|
+
l.should raise_error ArgumentError
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
describe AppEngine::Datastore::IMHandle do
|
311
|
+
it 'should support ==' do
|
312
|
+
a = AppEngine::Datastore::IMHandle.new(:unknown, 'foobar')
|
313
|
+
b = AppEngine::Datastore::IMHandle.new(:unknown, 'foobar')
|
314
|
+
a.should == b
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'should support symbols' do
|
318
|
+
p = Proc.new do
|
319
|
+
AppEngine::Datastore::IMHandle.new(:sip, "sip_address")
|
320
|
+
AppEngine::Datastore::IMHandle.new(:xmpp, "xmpp_address")
|
321
|
+
AppEngine::Datastore::IMHandle.new(:unknown, "unknown_address")
|
322
|
+
end
|
323
|
+
p.should_not raise_error ArgumentError
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'should support urls' do
|
327
|
+
protocol = 'http://aim.com/'
|
328
|
+
address = 'foobar'
|
329
|
+
im = AppEngine::Datastore::IMHandle.new(protocol, address)
|
330
|
+
im.protocol.should == protocol
|
331
|
+
im.address.should == address
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'should convert errors' do
|
335
|
+
l = lambda {AppEngine::Datastore::IMHandle.new 'aim', 'foobar'}
|
336
|
+
l.should raise_error ArgumentError
|
337
|
+
end
|
338
|
+
end
|