appengine-apis 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ == 0.0.2 2009-04-15
2
+
3
+ * Add URLFetch and Users APIs
4
+
1
5
  == 0.0.1 2009-04-02
2
6
 
3
7
  * Initial release
@@ -10,6 +10,8 @@ lib/appengine-apis/datastore_types.rb
10
10
  lib/appengine-apis/logger.rb
11
11
  lib/appengine-apis/merb-logger.rb
12
12
  lib/appengine-apis/testing.rb
13
+ lib/appengine-apis/urlfetch.rb
14
+ lib/appengine-apis/users.rb
13
15
  script/console
14
16
  script/destroy
15
17
  script/generate
@@ -18,4 +20,6 @@ spec/datastore_types_spec.rb
18
20
  spec/logger_spec.rb
19
21
  spec/spec.opts
20
22
  spec/spec_helper.rb
23
+ spec/urlfetch_spec.rb
24
+ spec/users_spec.rb
21
25
  tasks/rspec.rake
@@ -6,6 +6,16 @@
6
6
 
7
7
  APIs and utilities for using JRuby on Google App Engine.
8
8
 
9
+ See these classes for an overview of each API:
10
+ - AppEngine::Logger
11
+ - AppEngine::Testing
12
+ - AppEngine::Users
13
+ - AppEngine::URLFetch
14
+ - AppEngine::Datastore
15
+
16
+ Unless you're implementing your own ORM, you probably want to use the
17
+ DataMapper API instead of the lower level AppEngine::Datastore API.
18
+
9
19
  == REQUIREMENTS:
10
20
 
11
21
  * Google App Engine SDK for Java (http://code.google.com/appengine)
@@ -19,5 +19,5 @@ $:.unshift(File.dirname(__FILE__)) unless
19
19
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
20
20
 
21
21
  module AppEngine
22
- VERSION = '0.0.1'
22
+ VERSION = '0.0.2'
23
23
  end
@@ -29,12 +29,12 @@ require 'appengine-apis/datastore_types'
29
29
 
30
30
  module AppEngine
31
31
 
32
- # The +Datastore+ provides access to a schema-less data
32
+ # The Datastore provides access to a schema-less data
33
33
  # storage system. The fundamental unit of data in this system is the
34
- # +Entity+, which has an immutable identity (represented by a
35
- # +Key+) and zero of more mutable properties. +Entity+
34
+ # Datastore::Entity, which has an immutable identity (represented by a
35
+ # Datastore::Key) and zero of more mutable properties. Entity
36
36
  # objects can be created, updated, deleted, retrieved by identifier,
37
- # and queried via a combination of properties.
37
+ # and queried via a combination of properties using Datastore::Query.
38
38
  #
39
39
  # The +Datastore+ can be used transactionally and supports the
40
40
  # notion of a "current" transaction. A current transaction is established by
@@ -47,6 +47,21 @@ module AppEngine
47
47
  # Users of this class have the choice of explicitly passing a (potentially
48
48
  # null) +Transaction+ to these methods or relying on the current transaction.
49
49
  #
50
+ # Supported property types:
51
+ # - String (max 500 chars)
52
+ # - Integer ((-2**63)..(2**63 - 1))
53
+ # - Float
54
+ # - Time
55
+ # - TrueClass
56
+ # - FalseClass
57
+ # - NilClass
58
+ # - Datastore::Key
59
+ # - Datastore::Link
60
+ # - Datastore::Text
61
+ # - Datastore::Blob
62
+ # - Datastore::ByteString
63
+ # - com.google.appengine.api.users.User
64
+
50
65
  module Datastore
51
66
  module_function
52
67
 
@@ -254,11 +269,14 @@ module Datastore
254
269
  FetchOptions = JavaDatastore::FetchOptions
255
270
 
256
271
  module Constants
257
- [JQuery::FilterOperator, JQuery::SortDirection].each do |enum|
258
- enum.constants.each do |name|
259
- const_set(name, enum.const_get(name))
260
- end
261
- end
272
+ EQUAL = JQuery::FilterOperator::EQUAL
273
+ GREATER_THAN = JQuery::FilterOperator::GREATER_THAN
274
+ GREATER_THAN_OR_EQUAL = JQuery::FilterOperator::GREATER_THAN_OR_EQUAL
275
+ LESS_THAN = JQuery::FilterOperator::LESS_THAN
276
+ LESS_THAN_OR_EQUAL = JQuery::FilterOperator::LESS_THAN_OR_EQUAL
277
+
278
+ ASCENDING = JQuery::SortDirection::ASCENDING
279
+ DESCENDING = JQuery::SortDirection::DESCENDING
262
280
  end
263
281
  include Constants
264
282
 
@@ -35,20 +35,42 @@ module AppEngine
35
35
  module Datastore
36
36
  JavaDatastore = Java.ComGoogleAppengineApiDatastore
37
37
 
38
+ # Base class of Datastore Errors
38
39
  class Error < StandardError; end
40
+
41
+ # Raised when a query requires a Composite index that does not exist
39
42
  class NeedIndex < Error; end
43
+
44
+ # The datastore operation timed out. This can happen when you attempt to
45
+ # put, get, or delete too many entities or an entity with too many
46
+ # properties, or if the datastore is overloaded or having trouble.
40
47
  class Timeout < Error; end
48
+
49
+ # An internal datastore error. Please report this to Google.
41
50
  class InternalError < Error; end
51
+
52
+ # May be raised during a call to #transaction to abort and rollback the
53
+ # transaction. Note that *any* exception raised by a transaction
54
+ # block will cause a rollback. This is purely for convenience.
42
55
  class Rollback < Error; end
56
+
57
+ # Raised when a transaction could not be committed, usually due to
58
+ # contention.
43
59
  class TransactionFailed < Error; end
60
+
61
+ # Raised by #get when the requested entity is not found.
44
62
  class EntityNotFound < Error; end
63
+
64
+ # Raised by Datastore::Query.entity if the query returns more
65
+ # than one entity
45
66
  class TooManyResults < Error; end
46
67
 
47
- #A long string type.
68
+ # A long string type.
48
69
  #
49
70
  # Strings of any length can be stored in the datastore using this
50
71
  # type.
51
72
  #
73
+ # Not indexed.
52
74
  class Text < String
53
75
  def to_java
54
76
  JavaDatastore::Text.new(self.to_java_string)
@@ -60,6 +82,7 @@ module AppEngine
60
82
  end
61
83
 
62
84
  # A blob type, appropriate for storing binary data of any length.
85
+ # Not indexed.
63
86
  class Blob < String
64
87
  def to_java
65
88
  JavaDatastore::Blob.new(self.to_java_bytes)
@@ -19,10 +19,23 @@
19
19
  # Replacement for the standard logger.rb which uses the Google App Engine
20
20
  # logging API.
21
21
 
22
+
22
23
  require 'appengine-apis/apiproxy'
23
24
  require 'logger'
24
25
 
25
26
  module AppEngine
27
+
28
+ # Replacement for the standard logger.rb. Saves logs to the App Engine
29
+ # Dashboard (or to the java logging api when running locally).
30
+ #
31
+ # logger = AppEngine::Logger.new
32
+ # logger.warn "foobar"
33
+ #
34
+ # or (for compatibility with code already using logger.rb)
35
+ #
36
+ # Logger = AppEngine::Logger
37
+ # logger = Logger.new(stream) # stream is ignored
38
+ # logger.info "Hello, dashboard"
26
39
  class Logger < ::Logger
27
40
  SEVERITIES = {
28
41
  DEBUG => ApiProxy::LogRecord::Level::debug,
@@ -20,7 +20,10 @@
20
20
 
21
21
  require 'merb-core/logger'
22
22
 
23
- module Merb
23
+ module Merb # :nodoc:
24
+
25
+ # Modifies the Merb Logger class to save logs using the Logging API
26
+ # instead of writing directly to a stream.
24
27
  class Logger
25
28
  def <<(string = nil)
26
29
  AppEngine::ApiProxy.log(
@@ -18,9 +18,18 @@
18
18
  #
19
19
  # Helpers for installing stub apis in unit tests.
20
20
 
21
+
21
22
  require 'appengine-apis/apiproxy'
22
23
 
23
24
  module AppEngine
25
+
26
+ # Local testing support for Google App Engine
27
+ #
28
+ # If you run your code on Google's servers or under dev_appserver,
29
+ # the api's are already configured.
30
+ #
31
+ # To run outside this environment, you need to install a test environment and
32
+ # api stubs.
24
33
  module Testing
25
34
  import com.google.appengine.tools.development.ApiProxyLocalFactory
26
35
  import com.google.appengine.api.datastore.dev.LocalDatastoreService
@@ -28,7 +37,7 @@ module AppEngine
28
37
  class TestEnv # :nodoc:
29
38
  include AppEngine::ApiProxy::Environment
30
39
 
31
- attr_writer :appid, :version, :email, :admin, :logged_in
40
+ attr_writer :appid, :version, :email, :admin
32
41
  attr_writer :auth_domain, :request_namespace, :default_namespace
33
42
 
34
43
  def initialize
@@ -50,11 +59,11 @@ module AppEngine
50
59
  end
51
60
 
52
61
  def isLoggedIn
53
- @logged_in
62
+ !(@email.nil? || @auth_domain.nil?)
54
63
  end
55
64
 
56
65
  def isAdmin
57
- @admin
66
+ !!@admin
58
67
  end
59
68
 
60
69
  def getAuthDomain
@@ -75,26 +84,43 @@ module AppEngine
75
84
  end
76
85
 
77
86
  class << self
87
+
88
+ # Install a test environment for the current thread.
89
+ #
90
+ # You must call this before making any api calls.
91
+ #
92
+ # Note that Google's production and local environments are
93
+ # single threaded. You may run into problems if you use multiple
94
+ # threads.
78
95
  def install_test_env
79
96
  env = TestEnv.new
80
97
  ApiProxy::setEnvironmentForCurrentThread(env)
81
98
  env
82
99
  end
83
100
 
84
- def factory
101
+ def factory # :nodoc:
85
102
  @factory ||= ApiProxyLocalFactory.new
86
103
  end
87
104
 
105
+ # The application directory, or '.' if not set.
106
+ # Composite index definitions are written to "#{app_dir}/WEB-INF/".
88
107
  def app_dir
89
108
  file = factory.getApplicationDirectory
90
109
  file && file.path
91
110
  end
92
-
111
+
112
+ # Sets the application directory. Should be called before
113
+ # creating stubs.
114
+ #
115
+ # Composite index definitions are written to "#{app_dir}/WEB-INF/".
93
116
  def app_dir=(dir)
94
117
  factory.setApplicationDirectory(java.io.File.new(dir))
95
118
  end
96
119
 
97
- # Force all datastore operations to use an in-memory datastore.
120
+ # Install stub apis and force all datastore operations to use
121
+ # an in-memory datastore.
122
+ #
123
+ # You may call this multiple times to reset to a new in-memory datastore.
98
124
  def install_test_datastore
99
125
  self.app_dir = '.' if app_dir.nil?
100
126
  delegate = factory.create
@@ -110,6 +136,12 @@ module AppEngine
110
136
  delegate
111
137
  end
112
138
 
139
+ # Install stub apis. The datastore will be written to the disk
140
+ # inside #app_dir.
141
+ #
142
+ # You could potentially use this to run under a ruby web server
143
+ # instead of dev_appserver. In that case you will need to install
144
+ # and configure a test environment for each request.
113
145
  def install_api_stubs
114
146
  self.app_dir = '.' if app_dir.nil?
115
147
  delegate = factory.create
@@ -0,0 +1,226 @@
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
+
19
+ require 'net/https'
20
+
21
+
22
+ module AppEngine
23
+ # The URLFetch Service provides a way for user code to execute HTTP requests
24
+ # to external URLs.
25
+ #
26
+ # Chunked and hanging requests are not supported, and all content will be
27
+ # returned in a single block.
28
+ module URLFetch
29
+ import com.google.appengine.api.urlfetch.FetchOptions
30
+ import com.google.appengine.api.urlfetch.HTTPHeader
31
+ import com.google.appengine.api.urlfetch.HTTPMethod
32
+ import com.google.appengine.api.urlfetch.HTTPRequest
33
+ import com.google.appengine.api.urlfetch.ResponseTooLargeException
34
+ import com.google.appengine.api.urlfetch.URLFetchServiceFactory
35
+
36
+ # Raised if the remote service could not be contacted
37
+ class DownloadError < StandardError; end
38
+
39
+ # Raised if the url cannot be parsed.
40
+ class InvalidURLError < StandardError; end
41
+
42
+ # Raised if the response is too large.
43
+ class ResponseTooLargeError < StandardError; end
44
+
45
+ module_function
46
+
47
+ #Fetches the given HTTP URL, blocking until the result is returned.
48
+ #
49
+ # Supported options:
50
+ # [:method] GET, POST, HEAD, PUT, or DELETE
51
+ # [:payload] POST or PUT payload (implies method is not GET, HEAD,
52
+ # or DELETE)
53
+ # [:headers]
54
+ # HTTP headers to send with the request. May be a Hash or
55
+ # Net::HTTPHeaders.
56
+ # [:allow_truncated]
57
+ # if true, truncate large responses and return them
58
+ # without error. otherwise, ResponseTooLargeError will be thrown when a
59
+ # response is truncated.
60
+ # [:follow_redirects]
61
+ # if true (the default), redirects are transparently followed and the
62
+ # response (if less than 5 redirects) contains the final destination's
63
+ # payload and the response status is 200. You lose, however, the
64
+ # redirect chaininformation. If false, you see the HTTP response
65
+ # yourself, including the 'Location' header, and redirects are not
66
+ # followed.
67
+ #
68
+ # Returns a Net::HTTPResponse.
69
+ #
70
+ # Throws:
71
+ # - InvalidURLError if the provided url is malformed.
72
+ # - DownloadError if the remote service could not be contacted or the URL
73
+ # could not be fetched.
74
+ # - ResponseTooLargeError if response truncation has been disabled and the
75
+ # response is too large. Some responses are too large to even retrieve
76
+ # from the remote server, and in these cases the exception is thrown even
77
+ # if response truncation is enabled.
78
+ #
79
+ def fetch(url, options={})
80
+ request = build_urlfetch_request(url, options)
81
+ begin
82
+ java_response = urlfetch_service.fetch(request)
83
+ return convert_urlfetch_body(java_response)
84
+ rescue java.lang.IllegalArgumentException => ex
85
+ raise ArgumentError, ex.message
86
+ rescue java.net.MalformedURLException => ex
87
+ raise InvalidURLError, ex.message
88
+ rescue java.io.IOException => ex
89
+ raise DownloadError, ex.message
90
+ rescue ResponseTooLargeException => ex
91
+ raise ResponseTooLargeError, ex.message
92
+ end
93
+ end
94
+
95
+ def build_urlfetch_request(url, options) # :nodoc:
96
+ method = options.delete(:method) || 'GET'
97
+ payload = options.delete(:payload)
98
+ headers = options.delete(:headers) || {}
99
+ truncate = options.delete(:allow_truncated)
100
+ follow_redirects = options.delete(:follow_redirects) || true
101
+
102
+ unless options.empty?
103
+ raise ArgumentError, "Unsupported options #{options.inspect}."
104
+ end
105
+
106
+ begin
107
+ method = HTTPMethod.value_of(method.to_s.upcase)
108
+ rescue java.lang.IllegalArgumentException
109
+ raise ArgumentError, "Invalid method #{method.inspect}."
110
+ end
111
+
112
+ if truncate
113
+ options = FetchOptions::Builder.allow_truncate
114
+ else
115
+ options = FetchOptions::Builder.disallow_truncate
116
+ end
117
+ if follow_redirects
118
+ options.follow_redirects
119
+ else
120
+ options.do_not_follow_redirects
121
+ end
122
+
123
+ url = java.net.URL.new(url) unless url.java_kind_of? java.net.URL
124
+ request = HTTPRequest.new(url, method, options)
125
+
126
+ iterator = if headers.respond_to?(:canonical_each)
127
+ :canonical_each
128
+ else
129
+ :each
130
+ end
131
+
132
+ headers.send(iterator) do |name, value|
133
+ request.set_header(HTTPHeader.new(name, value))
134
+ end
135
+
136
+ if payload
137
+ request.set_payload(payload.as_java_bytes)
138
+ end
139
+
140
+ return request
141
+ end
142
+
143
+ def convert_urlfetch_body(java_response) # :nodoc:
144
+ status = java_response.response_code.to_s
145
+ klass = Net::HTTPResponse.send(:response_class, status)
146
+ mesg = klass.name
147
+ mesg = mesg[4, mesg.size]
148
+ response = klass.new(nil, status, mesg)
149
+ java_response.headers.each do |header|
150
+ response.add_field(header.name, header.value)
151
+ end
152
+ body = if java_response.content
153
+ String.from_java_bytes(java_response.content)
154
+ else
155
+ nil
156
+ end
157
+ response.urlfetch_body = body
158
+ return response
159
+ end
160
+
161
+ def urlfetch_service # :nodoc:
162
+ @service ||= URLFetchServiceFactory.getURLFetchService
163
+ end
164
+
165
+ def urlfetch_service=(service) # :nodoc:
166
+ @service = service
167
+ end
168
+
169
+
170
+ # A subclass of Net::HTTP that makes requests using Google App Engine's
171
+ # URLFetch Service.
172
+ #
173
+ # To replace the standard implementation throughout your app you can do:
174
+ # require 'appengine-apis/urlfetch'
175
+ # Net::HTTP = AppEngine::URLFetch::HTTP
176
+ class HTTP < Net::HTTP
177
+ alias connect on_connect
178
+
179
+ def request(req, body=nil, &block)
180
+ begin
181
+ proto = use_ssl? ? 'https' : 'http'
182
+ url = "#{proto}://#{addr_port}#{req.path}"
183
+ options = {
184
+ :payload => body,
185
+ :follow_redirects => false,
186
+ :allow_truncated => true,
187
+ :method => req.method,
188
+ :headers => req
189
+ }
190
+ res = URLFetch.fetch(url, options)
191
+ end while res.kind_of?(Net::HTTPContinue)
192
+ res.reading_body(nil, req.response_body_permitted?) {
193
+ yield res if block_given?
194
+ }
195
+ return res
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ module Net # :nodoc:
202
+ class HTTPResponse # :nodoc:
203
+ alias stream_check_without_urlfetch stream_check
204
+
205
+ def stream_check
206
+ return if @urlfetch_body
207
+ stream_check_without_urlfetch
208
+ end
209
+
210
+ alias read_body_0_without_urlfetch read_body_0
211
+
212
+ def read_body_0(dest)
213
+ if @urlfetch_body
214
+ dest << @urlfetch_body
215
+ return
216
+ else
217
+ read_body_0_without_urlfetch(dest)
218
+ end
219
+ end
220
+
221
+ def urlfetch_body=(body)
222
+ @body_exist = body && self.class.body_permitted?
223
+ @urlfetch_body = body || ''
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,105 @@
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
+
19
+ module AppEngine
20
+ # Users provides information useful for forcing a user to log in or out, and
21
+ # retrieving information about the user who is currently logged-in.
22
+ module Users
23
+ import com.google.appengine.api.users.User
24
+ import com.google.appengine.api.users.UserServiceFactory
25
+
26
+ Service = UserServiceFactory.getUserService
27
+
28
+ class << self
29
+
30
+ # If the user is logged in, this method will return a User that contains
31
+ # information about them. Note that repeated calls may not necessarily
32
+ # return the same User object.
33
+ def current_user
34
+ Service.current_user
35
+ end
36
+
37
+ # Computes the login URL for this request and specified destination URL.
38
+ #
39
+ # Args:
40
+ # - dest_url: The desired final destination URL for the user
41
+ # once login is complete. If +dest_url+ does not have a host
42
+ # specified, we will use the host from the current request.
43
+ def create_login_url(url)
44
+ Service.create_login_url(url)
45
+ end
46
+
47
+ # Computes the logout URL for this request and specified destination URL.
48
+ #
49
+ # Args:
50
+ # - dest_url: String that is the desired final destination URL for the
51
+ # user once logout is complete. If +dest_url+ does not have
52
+ # a host specified, uses the host from the current request.
53
+ def create_logout_url(url)
54
+ Service.create_logout_url(url)
55
+ end
56
+
57
+ # Returns true if there is a user logged in, false otherwise.
58
+ def logged_in?
59
+ Service.is_user_logged_in?
60
+ end
61
+
62
+ # Returns true if the user making this request is an admin for this
63
+ # application, false otherwise.
64
+ #
65
+ # This is a separate function, and not a member function of the User
66
+ # class, because admin status is not persisted in the datastore. It
67
+ # only exists for the user making this request right now.
68
+ def admin?
69
+ Service.is_user_admin?
70
+ end
71
+ end
72
+
73
+ # User represents a specific user, represented by the combination of an
74
+ # email address and a specific Google Apps domain (which we call an
75
+ # auth_domain). For normal Google login, authDomain will be set to
76
+ # "gmail.com".
77
+ class User
78
+ alias == equals?
79
+ alias to_s toString
80
+
81
+ if nil # rdoc only
82
+ # Creates a new User.
83
+ #
84
+ # Args:
85
+ # - email: a non-nil email address.
86
+ # - auth_domain: a non-nil domain name into which this user has
87
+ # authenticated, or "gmail.com" for normal Google authentication.
88
+ def initialize(email, auth_domain)
89
+ end
90
+
91
+ # Return this user's nickname. The nickname will be a unique, human
92
+ # readable identifier for this user with respect to this application.
93
+ # It will be an email address for some users, but not all.
94
+ def nickname
95
+ end
96
+
97
+ def auth_domain
98
+ end
99
+
100
+ def email
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1 +1 @@
1
- --colour
1
+ --colour --backtrace
@@ -0,0 +1,152 @@
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
+
19
+
20
+ require File.dirname(__FILE__) + '/spec_helper.rb'
21
+ require 'appengine-apis/urlfetch'
22
+ require 'uri'
23
+
24
+ module AppEngine::URLFetch
25
+ class FakeResponse
26
+ def initialize(status, body, headers)
27
+ @headers = java.util.ArrayList.new
28
+ headers.each do |k, v|
29
+ @headers << HTTPHeader.new(k, v)
30
+ end
31
+ @content = body.to_java_bytes if body
32
+ @status = status
33
+ end
34
+
35
+ def getResponseCode
36
+ @status
37
+ end
38
+ alias get_response_code getResponseCode
39
+ alias response_code getResponseCode
40
+
41
+ def getContent
42
+ @content
43
+ end
44
+ alias get_content getContent
45
+ alias content getContent
46
+
47
+ def getHeaders
48
+ @headers
49
+ end
50
+ alias get_headers getHeaders
51
+ alias headers getHeaders
52
+ end
53
+ end
54
+
55
+ describe AppEngine::URLFetch do
56
+ HTTPRequest = com.google.appengine.api.urlfetch.HTTPRequest
57
+
58
+ before :each do
59
+ AppEngine::URLFetch.urlfetch_service = mock("URLFetchService")
60
+ @url = 'http://foo.example.com/'
61
+ end
62
+
63
+ def fetch_and_return(status, body='body', headers={}, &block)
64
+ response = AppEngine::URLFetch::FakeResponse.new(status, body, headers)
65
+ if block
66
+ AppEngine::URLFetch.urlfetch_service.should_receive(:fetch) do |req|
67
+ yield req
68
+ response
69
+ end
70
+ else
71
+ matcher = AppEngine::URLFetch.urlfetch_service.should_receive(:fetch)
72
+ matcher.with(kind_of(HTTPRequest)).and_return(response)
73
+ end
74
+ end
75
+
76
+ it "should fetch correct url" do
77
+ fetch_and_return(200) do |req|
78
+ req.url.to_string.should == @url
79
+ end
80
+ AppEngine::URLFetch.fetch(@url)
81
+ end
82
+
83
+ it "should read ok status" do
84
+ fetch_and_return(200)
85
+
86
+ AppEngine::URLFetch.fetch(@url).should be_a(Net::HTTPOK)
87
+ end
88
+
89
+ it "should read error status" do
90
+ fetch_and_return(500)
91
+ AppEngine::URLFetch.fetch(@url).should be_a(Net::HTTPInternalServerError)
92
+ end
93
+
94
+ it "should read body" do
95
+ fetch_and_return(200, 'foo')
96
+ AppEngine::URLFetch.fetch(@url).body.should == 'foo'
97
+ end
98
+
99
+ it "should read binary body" do
100
+ fetch_and_return(200, 'bar\0')
101
+ AppEngine::URLFetch.fetch(@url).body.should == 'bar\0'
102
+ end
103
+
104
+ # TODO can't read fetch options, so there's no way to tell if they're set
105
+ end
106
+
107
+ describe AppEngine::URLFetch::HTTP do
108
+ HTTPRequest = com.google.appengine.api.urlfetch.HTTPRequest
109
+
110
+ before :each do
111
+ AppEngine::URLFetch.urlfetch_service = mock("URLFetchService")
112
+ @url = 'http://foo.example.com/'
113
+ end
114
+
115
+ def fetch_and_return(status, body='body', headers={}, &block)
116
+ response = AppEngine::URLFetch::FakeResponse.new(status, body, headers)
117
+ if block
118
+ AppEngine::URLFetch.urlfetch_service.should_receive(:fetch) do |req|
119
+ yield req
120
+ response
121
+ end
122
+ else
123
+ matcher = AppEngine::URLFetch.urlfetch_service.should_receive(:fetch)
124
+ matcher.with(kind_of(HTTPRequest)).and_return(response)
125
+ end
126
+ end
127
+
128
+ it "should fetch correct url" do
129
+ fetch_and_return(200) do |req|
130
+ req.url.to_string.should == @url
131
+ end
132
+ AppEngine::URLFetch::HTTP.get URI.parse(@url)
133
+ end
134
+
135
+
136
+ it "should read body" do
137
+ fetch_and_return(200, 'foo')
138
+ AppEngine::URLFetch::HTTP.get(URI.parse(@url)).should == 'foo'
139
+ end
140
+
141
+ it "should support https" do
142
+ @url = 'https://foo.example.com/'
143
+ fetch_and_return(200, 'secure') do |req|
144
+ req.url.to_string.should == @url
145
+ end
146
+
147
+ uri = URI.parse(@url)
148
+ http = AppEngine::URLFetch::HTTP.new(uri.host, uri.port)
149
+ http.use_ssl = true
150
+ http.get(uri.path).body.should == 'secure'
151
+ end
152
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright:: Copyright 2009 Google Inc.
2
+ # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require File.dirname(__FILE__) + '/spec_helper.rb'
17
+ require 'appengine-apis/users'
18
+
19
+ describe AppEngine::Users do
20
+ before :all do
21
+ AppEngine::Testing::install_api_stubs
22
+ end
23
+
24
+ before :each do
25
+ @env = AppEngine::Testing::install_test_env
26
+ @env.email = 'foo@example.com'
27
+ end
28
+
29
+ it "should have default auth_domain" do
30
+ user = AppEngine::Users.current_user
31
+ user.email.should == 'foo@example.com'
32
+ user.auth_domain.should == 'gmail.com'
33
+ end
34
+
35
+ it 'should read auth_domain' do
36
+ @env.auth_domain = 'example.com'
37
+ AppEngine::Users.current_user.auth_domain.should == 'example.com'
38
+ end
39
+
40
+ it 'should read admin' do
41
+ AppEngine::Users.admin?.should == false
42
+ @env.admin = true
43
+ AppEngine::Users.admin?.should == true
44
+ end
45
+
46
+ it 'should set logged_in?' do
47
+ AppEngine::Users.logged_in?.should == true
48
+ @env.email = nil
49
+ AppEngine::Users.logged_in?.should == false
50
+ end
51
+
52
+ it 'should create urls' do
53
+ login = AppEngine::Users.create_login_url('/foobar')
54
+ logout = AppEngine::Users.create_logout_url('/foobaz')
55
+ login.should =~ /foobar/
56
+ logout.should =~ /foobaz/
57
+ end
58
+ end
metadata CHANGED
@@ -9,7 +9,7 @@ email:
9
9
  - ribrdb@gmail.com
10
10
  cert_chain: []
11
11
 
12
- summary: APIs and utilities for using JRuby on Google App Engine.
12
+ summary: APIs and utilities for using JRuby on Google App Engine
13
13
  post_install_message: PostInstall.txt
14
14
  extra_rdoc_files:
15
15
  - History.txt
@@ -26,7 +26,11 @@ autorequire:
26
26
  rubyforge_project: appengine-jruby
27
27
  executables: []
28
28
 
29
- description: APIs and utilities for using JRuby on Google App Engine.
29
+ description: 'APIs and utilities for using JRuby on Google App Engine. See these
30
+ classes for an overview of each API: - AppEngine::Logger - AppEngine::Testing -
31
+ AppEngine::Users - AppEngine::URLFetch - AppEngine::Datastore Unless you''re implementing
32
+ your own ORM, you probably want to use the DataMapper API instead of the lower level
33
+ AppEngine::Datastore API.'
30
34
  specification_version: 2
31
35
  default_executable:
32
36
  files:
@@ -42,6 +46,8 @@ files:
42
46
  - lib/appengine-apis/logger.rb
43
47
  - lib/appengine-apis/merb-logger.rb
44
48
  - lib/appengine-apis/testing.rb
49
+ - lib/appengine-apis/urlfetch.rb
50
+ - lib/appengine-apis/users.rb
45
51
  - script/console
46
52
  - script/destroy
47
53
  - script/generate
@@ -50,6 +56,8 @@ files:
50
56
  - spec/logger_spec.rb
51
57
  - spec/spec.opts
52
58
  - spec/spec_helper.rb
59
+ - spec/urlfetch_spec.rb
60
+ - spec/users_spec.rb
53
61
  - tasks/rspec.rake
54
62
  required_rubygems_version: !ruby/object:Gem::Requirement
55
63
  requirements:
@@ -64,12 +72,12 @@ requirements: []
64
72
 
65
73
  authors:
66
74
  - Ryan Brown
67
- date: 2009-04-10 07:00:00 +00:00
75
+ date: 2009-04-15 07:00:00 +00:00
68
76
  platform: ruby
69
77
  test_files: []
70
78
 
71
79
  version: !ruby/object:Gem::Version
72
- version: 0.0.1
80
+ version: 0.0.2
73
81
  require_paths:
74
82
  - lib
75
83
  dependencies: