appengine-apis 0.0.1 → 0.0.2

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.
@@ -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: