appengine-apis 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,6 +19,7 @@
19
19
  # Switches the Merb Logger class to use the Google App Engine logging API.
20
20
 
21
21
  require 'merb-core/logger'
22
+ require 'appengine-apis/apiproxy.rb'
22
23
 
23
24
  module Merb # :nodoc:
24
25
 
@@ -0,0 +1,93 @@
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 'java'
20
+
21
+ module AppEngine
22
+
23
+ # Helper methods to locate the App Engine SDK and add it to the class path.
24
+ module SDK
25
+ class << self
26
+
27
+ # Tries to load the ApiProxy class.
28
+ def load_apiproxy
29
+ with_jars(%w{lib impl appengine-api.jar}) do
30
+ return Java.ComGoogleApphostingApi.ApiProxy
31
+ end
32
+ end
33
+
34
+ # Tries to load the ApiProxyLocalFactory class.
35
+ def load_local_apiproxy_factory
36
+ with_jars(%w{lib shared appengine-local-runtime-shared.jar},
37
+ %w{lib impl appengine-api-stubs.jar},
38
+ %w{lib impl appengine-local-runtime.jar}
39
+ ) do
40
+ return Java.ComGoogleAppengineToolsDevelopment.ApiProxyLocalFactory
41
+ end
42
+ end
43
+
44
+ def with_jars(*jars) # :nodoc:
45
+ begin
46
+ failed = false
47
+ yield
48
+ rescue NameError => ex
49
+ if failed
50
+ raise ex
51
+ else
52
+ failed = true
53
+ jars.each do |jar|
54
+ $CLASSPATH << sdk_path(*jar)
55
+ end
56
+ retry
57
+ end
58
+ end
59
+ end
60
+
61
+ # Tries to find the Google App Engine SDK for Java.
62
+ #
63
+ # Looks for appcfg.sh in these directories (in order):
64
+ # - ENV['APPENGINE_JAVA_SDK']/bin
65
+ # - each directory in ENV['PATH']
66
+ # - '/usr/local/appengine-java-sdk/bin'
67
+ # - 'c:\appengine-java-sdk\bin'
68
+ #
69
+ # Returns File.join(sdk_directory, *pieces)
70
+ #
71
+ def sdk_path(*pieces)
72
+ puts "sdk_path(#{pieces.join('/')})"
73
+ unless @sdk_path
74
+ base_path = File.join(ENV['APPENGINE_JAVA_SDK'] || '', 'bin')
75
+ exec_paths = ENV['PATH'].split(File::PATH_SEPARATOR)
76
+ exec_paths << '/usr/local/appengine-java-sdk/bin'
77
+ exec_paths << 'c:\appengine-java-sdk\bin'
78
+ # TODO also check for reggae
79
+
80
+ while !exec_paths.empty?
81
+ if File.exist?(File.join(base_path, 'appcfg.sh'))
82
+ @sdk_path = File.dirname(base_path)
83
+ return File.join(@sdk_path, *pieces)
84
+ end
85
+ base_path = exec_paths.shift
86
+ end
87
+ raise "Unable to locate the Google App Engine SDK for Java."
88
+ end
89
+ File.join(@sdk_path, *pieces)
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/ruby1.8 -w
2
2
  #
3
- # Copyright:: Copyright 2007 Google Inc.
3
+ # Copyright:: Copyright 2009 Google Inc.
4
4
  # Original Author:: Ryan Brown (mailto:ribrdb@google.com)
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,8 +31,6 @@ module AppEngine
31
31
  # To run outside this environment, you need to install a test environment and
32
32
  # api stubs.
33
33
  module Testing
34
- import com.google.appengine.tools.development.ApiProxyLocalFactory
35
- import com.google.appengine.api.datastore.dev.LocalDatastoreService
36
34
 
37
35
  class TestEnv # :nodoc:
38
36
  include AppEngine::ApiProxy::Environment
@@ -43,7 +41,9 @@ module AppEngine
43
41
  def initialize
44
42
  @appid = "test"
45
43
  @version = "1.0"
46
- @auth_domain = "gmail.com"
44
+ @auth_domain = @request_namespace = "gmail.com"
45
+ @default_namespace = ""
46
+ @email = ""
47
47
  end
48
48
 
49
49
  def getAppId
@@ -59,7 +59,7 @@ module AppEngine
59
59
  end
60
60
 
61
61
  def isLoggedIn
62
- !(@email.nil? || @auth_domain.nil?)
62
+ !(@email.nil? || @auth_domain.nil? || @email.empty?)
63
63
  end
64
64
 
65
65
  def isAdmin
@@ -80,7 +80,11 @@ module AppEngine
80
80
 
81
81
  def setDefaultNamespace(s)
82
82
  @default_namespace = s
83
- end
83
+ end
84
+
85
+ def inspect
86
+ "#<TestEnv>"
87
+ end
84
88
  end
85
89
 
86
90
  class << self
@@ -99,7 +103,7 @@ module AppEngine
99
103
  end
100
104
 
101
105
  def factory # :nodoc:
102
- @factory ||= ApiProxyLocalFactory.new
106
+ @factory ||= AppEngine::SDK.load_local_apiproxy_factory.new
103
107
  end
104
108
 
105
109
  # The application directory, or '.' if not set.
@@ -124,13 +128,15 @@ module AppEngine
124
128
  def install_test_datastore
125
129
  self.app_dir = '.' if app_dir.nil?
126
130
  delegate = factory.create
131
+ lds = Java.ComGoogleAppengineApiDatastoreDev.LocalDatastoreService
132
+
127
133
  delegate.set_property(
128
- LocalDatastoreService::NO_STORAGE_PROPERTY, "true")
134
+ lds::NO_STORAGE_PROPERTY, "true")
129
135
  delegate.set_property(
130
- LocalDatastoreService::MAX_QUERY_LIFETIME_PROPERTY,
136
+ lds::MAX_QUERY_LIFETIME_PROPERTY,
131
137
  java.lang.Integer::MAX_VALUE.to_s)
132
138
  delegate.set_property(
133
- LocalDatastoreService::MAX_TRANSACTION_LIFETIME_PROPERTY,
139
+ lds::MAX_TRANSACTION_LIFETIME_PROPERTY,
134
140
  java.lang.Integer::MAX_VALUE.to_s)
135
141
  ApiProxy::setDelegate(delegate)
136
142
  delegate
@@ -149,6 +155,45 @@ module AppEngine
149
155
  delegate
150
156
  end
151
157
 
158
+ # Loads stub API implementations if no API implementation is
159
+ # currently configured.
160
+ #
161
+ # Sets up a datastore saved to disk in +app_dir+. +app_dir+ defaults
162
+ # to ENV['APPLICATION_ROOT'] or '.' if not specified.
163
+ #
164
+ # Does nothing is APIs are already configured (e.g. in production).
165
+ #
166
+ # As a shortcut you can use
167
+ # require 'appengine-apis/local_boot'
168
+ #
169
+ def boot(app_dir=nil)
170
+ app_dir ||= ENV['APPLICATION_ROOT'] || '.'
171
+ unless AppEngine::ApiProxy.current_environment
172
+ env = install_test_env
173
+ appid = get_app_id(app_dir)
174
+ env.appid = appid if appid
175
+ end
176
+ unless AppEngine::ApiProxy.delegate
177
+ self.app_dir = app_dir
178
+ install_api_stubs
179
+ end
180
+ end
181
+
182
+ # Looks for app.yaml or WEB-INF/appengine-web.xml in +app_dir+
183
+ # and parses the application id.
184
+ def get_app_id(app_dir)
185
+ aeweb_path = File.join(app_dir, 'WEB-INF', 'appengine-web.xml')
186
+ app_yaml_path = File.join(app_dir, 'app.yaml')
187
+ if File.exist?(app_yaml_path)
188
+ app_yaml = YAML.load(File.open(app_yaml_path))
189
+ return app_yaml['application']
190
+ elsif File.exist?(aeweb_path)
191
+ require 'rexml/document'
192
+ aeweb = REXML::Document.new(File.new(aeweb_path))
193
+ return aeweb.root.elements['application'].text
194
+ end
195
+ end
196
+
152
197
  end
153
198
 
154
199
  end
@@ -25,6 +25,11 @@ module AppEngine
25
25
  #
26
26
  # Chunked and hanging requests are not supported, and all content will be
27
27
  # returned in a single block.
28
+ #
29
+ # URLFetch::HTTP also provides a drop-in replacement for Net::HTTP.
30
+ # To replace the standard implementation throughout your app you can do:
31
+ # require 'appengine-apis/urlfetch'
32
+ # Net::HTTP = AppEngine::URLFetch::HTTP
28
33
  module URLFetch
29
34
  import com.google.appengine.api.urlfetch.FetchOptions
30
35
  import com.google.appengine.api.urlfetch.HTTPHeader
@@ -44,12 +49,12 @@ module AppEngine
44
49
 
45
50
  module_function
46
51
 
47
- #Fetches the given HTTP URL, blocking until the result is returned.
52
+ # Fetches the given HTTP URL, blocking until the result is returned.
48
53
  #
49
54
  # Supported options:
50
55
  # [:method] GET, POST, HEAD, PUT, or DELETE
51
56
  # [:payload] POST or PUT payload (implies method is not GET, HEAD,
52
- # or DELETE)
57
+ # or DELETE)
53
58
  # [:headers]
54
59
  # HTTP headers to send with the request. May be a Hash or
55
60
  # Net::HTTPHeaders.
@@ -194,5 +194,16 @@ describe AppEngine::Datastore::Entity do
194
194
  end
195
195
  props.should == {}
196
196
  end
197
-
197
+
198
+ it "should support update" do
199
+ @entity.update('foo' => 'bar', 'count' => 3)
200
+ @entity[:foo].should == 'bar'
201
+ @entity[:count].should == 3
202
+ end
203
+
204
+ it "should support to_hash" do
205
+ props = {'foo' => 'bar', 'count' => 3}
206
+ @entity.merge!(props)
207
+ @entity.to_hash.should == props
208
+ end
198
209
  end
@@ -0,0 +1,179 @@
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 File.dirname(__FILE__) + '/spec_helper.rb'
20
+ require 'appengine-apis/apiproxy'
21
+ require 'appengine-apis/mail'
22
+
23
+ MailMessage = JavaUtilities.get_proxy_or_package_under_package(
24
+ com.google.appengine.api.mail,
25
+ 'MailServicePb$MailMessage'
26
+ )
27
+
28
+ describe AppEngine::Mail do
29
+ before :each do
30
+ @delegate = mock_delegate
31
+ AppEngine::ApiProxy.set_delegate(@delegate)
32
+ end
33
+
34
+ def should_send(message)
35
+ should_make_call('Send', message)
36
+ end
37
+
38
+ def should_send_to_admins(message)
39
+ should_make_call('SendToAdmins', message)
40
+ end
41
+
42
+ def should_make_call(call, message)
43
+ @delegate.should_receive(:makeSyncCall).with(
44
+ anything, 'mail', call, proto(MailMessage, message))
45
+ end
46
+
47
+ it "should send simple message" do
48
+ should_send({
49
+ :sender => 'ribrdb@example.com',
50
+ :to => ['bob@example.com'],
51
+ :subject => 'Howdy',
52
+ :text_body => 'Sup?'
53
+ })
54
+ AppEngine::Mail.send(
55
+ 'ribrdb@example.com', 'bob@example.com',
56
+ 'Howdy', 'Sup?')
57
+ end
58
+
59
+ it "should support cc" do
60
+ should_send({
61
+ :sender => 'ribrdb@example.com',
62
+ :to => ['bob@example.com'],
63
+ :cc => ['fred@example.com'],
64
+ :subject => 'Howdy',
65
+ :text_body => 'Sup?'
66
+ })
67
+ AppEngine::Mail.send(
68
+ 'ribrdb@example.com', 'bob@example.com',
69
+ 'Howdy', 'Sup?', :cc => 'fred@example.com')
70
+ end
71
+
72
+ it "should support bcc" do
73
+ should_send({
74
+ :sender => 'ribrdb@example.com',
75
+ :to => ['bob@example.com'],
76
+ :bcc => ['fred@example.com'],
77
+ :subject => 'Howdy',
78
+ :text_body => 'Sup?'
79
+ })
80
+ AppEngine::Mail.send(
81
+ 'ribrdb@example.com', 'bob@example.com',
82
+ 'Howdy', 'Sup?', :bcc => 'fred@example.com')
83
+ end
84
+
85
+ it "should support multiple recipients" do
86
+ should_send({
87
+ :sender => 'ribrdb@example.com',
88
+ :to => ['bob@example.com', 'fred@example.com'],
89
+ :cc => ['bob@example.com', 'fred@example.com'],
90
+ :bcc => ['bob@example.com', 'fred@example.com'],
91
+ :subject => 'Howdy',
92
+ :text_body => 'Sup?'
93
+ })
94
+ AppEngine::Mail.send(
95
+ 'ribrdb@example.com', ['bob@example.com', 'fred@example.com'],
96
+ 'Howdy', 'Sup?',
97
+ :cc => ['bob@example.com', 'fred@example.com'],
98
+ :bcc => ['bob@example.com', 'fred@example.com'])
99
+ end
100
+
101
+ it "should support no to" do
102
+ should_send({
103
+ :sender => 'ribrdb@example.com',
104
+ :to => [],
105
+ :bcc => ['fred@example.com'],
106
+ :subject => 'Howdy',
107
+ :text_body => 'Sup?'
108
+ })
109
+ AppEngine::Mail.send(
110
+ 'ribrdb@example.com', nil,
111
+ 'Howdy', 'Sup?', :bcc => 'fred@example.com')
112
+ end
113
+
114
+ it "should support html" do
115
+ should_send({
116
+ :sender => 'ribrdb@example.com',
117
+ :to => ['bob@example.com'],
118
+ :subject => 'Howdy',
119
+ :text_body => 'Sup?',
120
+ :html_body => '<h1>Sup?</h1>',
121
+ })
122
+ AppEngine::Mail.send(
123
+ 'ribrdb@example.com', 'bob@example.com',
124
+ 'Howdy', 'Sup?', :html => '<h1>Sup?</h1>')
125
+ end
126
+
127
+ it "should support html only" do
128
+ should_send({
129
+ :sender => 'ribrdb@example.com',
130
+ :to => ['bob@example.com'],
131
+ :subject => 'Howdy',
132
+ :html_body => '<h1>Sup?</h1>',
133
+ })
134
+ AppEngine::Mail.send(
135
+ 'ribrdb@example.com', 'bob@example.com',
136
+ 'Howdy', nil, :html => '<h1>Sup?</h1>')
137
+ end
138
+
139
+ it "should support attachments hash" do
140
+ should_send({
141
+ :sender => 'ribrdb@example.com',
142
+ :to => ['bob@example.com'],
143
+ :subject => 'Howdy',
144
+ :text_body => 'Sup?',
145
+ :attachment => [
146
+ {:file_name => 'foo.gif', :data => 'foo'},
147
+ ]
148
+ })
149
+ AppEngine::Mail.send(
150
+ 'ribrdb@example.com', 'bob@example.com',
151
+ 'Howdy', 'Sup?', :attachments => {'foo.gif' => 'foo'})
152
+ end
153
+
154
+ it "should support attachments list" do
155
+ should_send({
156
+ :sender => 'ribrdb@example.com',
157
+ :to => ['bob@example.com'],
158
+ :subject => 'Howdy',
159
+ :text_body => 'Sup?',
160
+ :attachment => [
161
+ {:file_name => 'foo.gif', :data => 'foo1'},
162
+ {:file_name => 'foo.gif', :data => 'foo2'},
163
+ ]
164
+ })
165
+ AppEngine::Mail.send(
166
+ 'ribrdb@example.com', 'bob@example.com',
167
+ 'Howdy', 'Sup?',
168
+ :attachments => [['foo.gif', 'foo1'], ['foo.gif', 'foo2']])
169
+ end
170
+
171
+ it "should support sending to admins" do
172
+ should_send_to_admins({
173
+ :sender => 'ribrdb@example.com',
174
+ :subject => 'Howdy',
175
+ :text_body => 'Sup?'
176
+ })
177
+ AppEngine::Mail.send_to_admins('ribrdb@example.com', 'Howdy', 'Sup?')
178
+ end
179
+ end
@@ -0,0 +1,385 @@
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/memcache'
18
+
19
+ describe AppEngine::Memcache do
20
+ before :each do
21
+ AppEngine::Testing::install_test_datastore
22
+ @cache = AppEngine::Memcache.new
23
+ end
24
+
25
+ before :all do
26
+ AppEngine::Testing::install_test_env
27
+ end
28
+
29
+ describe 'get' do
30
+
31
+ it 'should return nil on miss' do
32
+ @cache.get('foobar').should == nil
33
+ @cache['foobar'].should == nil
34
+ end
35
+
36
+ it 'should support strings' do
37
+ @cache.set('foobar', 'test').should == true
38
+ @cache.get('foobar').should == 'test'
39
+ @cache['foobar'].should == 'test'
40
+ end
41
+
42
+ it 'should support multiple values' do
43
+ @cache.get('foo', 'bar', 'baz').should == [nil, nil, nil]
44
+ @cache['foo', 'bar', 'baz'].should == [nil, nil, nil]
45
+ @cache.set('bar', 'food').should == true
46
+ @cache['foo', 'bar', 'baz'].should == [nil, 'food', nil]
47
+ end
48
+
49
+ it 'should support getting an array of keys' do
50
+ @cache.set('foobar', 'test').should == true
51
+ @cache.get(['foobar', 'flowers']).should == ['test', nil]
52
+ @cache.get(['foobar']).should == ['test']
53
+ end
54
+
55
+ it 'should support numbers' do
56
+ @cache.set('one', 1).should == true
57
+ @cache.set('pi', 3.14).should == true
58
+ @cache.get('pi').should == 3.14
59
+ @cache.get('one').should === 1
60
+ end
61
+
62
+ it 'should support symbol keys' do
63
+ @cache.set(:a, 'A')
64
+ @cache.get(:a).should == 'A'
65
+ end
66
+
67
+ it 'should support booleans' do
68
+ @cache.set('true', true)
69
+ @cache.set('false', false)
70
+ @cache.get('true').should == true
71
+ @cache.get('false').should == false
72
+ end
73
+
74
+ it 'should support marshaled objects' do
75
+ @cache.set('a', 1..5)
76
+ @cache.get('a').should == (1..5)
77
+ end
78
+
79
+ it 'should support false keys' do
80
+ @cache.set(nil, 'nil')
81
+ @cache.set(false, 'false')
82
+ @cache.get(nil).should == 'nil'
83
+ @cache.get(false).should == 'false'
84
+ end
85
+ end
86
+
87
+ describe 'get_hash' do
88
+ it 'should not include missing keys' do
89
+ @cache.get_hash(:a, :b).should == {}
90
+ end
91
+
92
+ it 'should get objects' do
93
+ @cache.set('a', 'A')
94
+ @cache.set('b', 3)
95
+ @cache.get_hash('a', 'c', 'b').should == {'a' => 'A', 'b' => 3 }
96
+ end
97
+ end
98
+
99
+ describe 'clear' do
100
+ it 'should clear the cache' do
101
+ @cache.set(:a, 'A')
102
+ @cache.set('b', 'B')
103
+ @cache.clear
104
+ @cache.get_hash(:a, 'b', :c).should == {}
105
+ end
106
+ end
107
+
108
+ describe 'flush_all' do
109
+ it 'should clear the cache' do
110
+ @cache.set(:a, 'A')
111
+ @cache.set('b', 'B')
112
+ @cache.flush_all
113
+ @cache.get_hash(:a, 'b', :c).should == {}
114
+ end
115
+ end
116
+
117
+ describe 'stats' do
118
+ it 'should count items' do
119
+ @cache.stats[:items].should == 0
120
+ @cache.set(:a, 'A')
121
+ @cache.stats[:items].should == 1
122
+ end
123
+ end
124
+
125
+ describe 'delete' do
126
+ it 'should remove items' do
127
+ @cache.set(:a, 'A')
128
+ @cache.get(:a).should == 'A'
129
+ @cache.delete(:a).should == true
130
+ @cache.get(:a).should == nil
131
+ @cache.get_hash(:a).should == {}
132
+ end
133
+
134
+ it 'should return false on missing items' do
135
+ @cache.delete(:a).should == false
136
+ end
137
+
138
+ it 'should allow blocking adds' do
139
+ @cache.set(:a, '1')
140
+ @cache.set(:b, '2')
141
+ @cache.delete(:a)
142
+ @cache.delete(:b, 1)
143
+ @cache.add(:a, 'A').should == true
144
+ @cache.add(:b, 'B').should == false
145
+ end
146
+ end
147
+
148
+ describe 'delete_many' do
149
+ it 'should remove items' do
150
+ @cache.set(:a, 'A')
151
+ @cache.set(:b, 'B')
152
+ @cache.set(:c, 'C')
153
+ @cache.delete_many([:a, :c])
154
+ @cache.get_hash(:a, :b, :c).should == {:b => 'B'}
155
+ end
156
+
157
+ it 'should return removed keys' do
158
+ @cache.set('a', 'A')
159
+ @cache.set('b', 'B')
160
+ @cache.set('c', 'C')
161
+ removed = @cache.delete_many(['a', 'c'])
162
+ removed.sort.should == ['a', 'c']
163
+ end
164
+
165
+ it 'should not return missing keys' do
166
+ @cache.set('a', 'A')
167
+ @cache.set('b', 'B')
168
+ @cache.set('c', 'C')
169
+ removed = @cache.delete_many(['a', 'c', 'd', 'e'])
170
+ removed.sort.should == ['a', 'c']
171
+ @cache.delete_many(['e']).should == []
172
+ end
173
+
174
+
175
+ it 'should allow blocking adds' do
176
+ @cache.set(:a, '1')
177
+ @cache.set(:b, '2')
178
+ @cache.delete_many([:a])
179
+ @cache.delete([:b, :c], 1)
180
+ @cache.add(:a, 'A').should == true
181
+ @cache.add(:b, 'B').should == false
182
+ end
183
+ end
184
+
185
+ describe 'add' do
186
+ it 'should add missing' do
187
+ @cache.add(:a, 'A').should == true
188
+ @cache.get(:a).should == 'A'
189
+ end
190
+
191
+ it 'should not replace existing entries' do
192
+ @cache.set(:a, 1)
193
+ @cache.add(:a, 'A').should == false
194
+ @cache.get(:a).should == 1
195
+ end
196
+ end
197
+
198
+ describe 'add_many' do
199
+ it 'should add missing' do
200
+ @cache.add_many({:a => 1, :b =>2})
201
+ @cache.get(:b, :a).should == [2, 1]
202
+ end
203
+
204
+ it 'should not replace existing entries' do
205
+ @cache.set(:a, 1)
206
+ @cache.add_many({:a => 'A', :b => 'B'})
207
+ @cache.get(:a, :b).should == [1, 'B']
208
+ end
209
+
210
+ it 'should return existing keys' do
211
+ @cache.set(:a, 1)
212
+ @cache.add_many({:a => 'A', :b => 'B'}).should == [:a]
213
+ end
214
+ end
215
+
216
+ describe 'set' do
217
+ it 'should add missing' do
218
+ @cache.set(:a, :A).should == true
219
+ @cache.get(:a).should == :A
220
+ end
221
+
222
+ it 'should replace existing' do
223
+ @cache.set(:a, :A).should == true
224
+ @cache.get(:a).should == :A
225
+ @cache.set(:a, 1).should == true
226
+ @cache.get(:a).should == 1
227
+ end
228
+ end
229
+
230
+ describe 'set_many' do
231
+ it 'should set multiple values' do
232
+ @cache.set_many({:a => 1, :b => 2}).should == []
233
+ end
234
+ end
235
+
236
+ describe 'replace' do
237
+ it 'should replace existing' do
238
+ @cache.set(:a, :A).should == true
239
+ @cache.get(:a).should == :A
240
+ @cache.replace(:a, 1).should == true
241
+ @cache.get(:a).should == 1
242
+ end
243
+
244
+ it 'should not replace missing' do
245
+ @cache.replace(:a, :A).should == false
246
+ end
247
+ end
248
+
249
+ describe 'replace_many' do
250
+ it 'should replace many' do
251
+ @cache.set_many({:a => 1, :c => 3})
252
+ @cache.replace_many({:a => :A, :b => :B, :c => :C}).should == [:b]
253
+ @cache.get(:a, :b, :c).should == [:A, nil, :C]
254
+ end
255
+ end
256
+
257
+ describe 'dict access' do
258
+ it 'should support getting with []' do
259
+ @cache.set(:a, 7)
260
+ @cache[:a].should == 7
261
+ @cache.set(:b, :B)
262
+ @cache[:a, :b].should == [7, :B]
263
+ end
264
+
265
+ it 'should support setting with []=' do
266
+ @cache[:a, :b] = [1, :B]
267
+ @cache[:c] = 3
268
+ @cache[:a].should == 1
269
+ @cache[:b].should == :B
270
+ @cache[:c].should == 3
271
+ end
272
+ end
273
+
274
+ describe 'incr' do
275
+ it 'should increment number' do
276
+ @cache.set(:a, 1)
277
+ @cache.incr(:a).should == 2
278
+ @cache.incr(:a, 7).should == 9
279
+ @cache.get(:a).should == 9
280
+ end
281
+
282
+ it 'should wrap' do
283
+ @cache.set(:a, 7)
284
+ @cache.incr(:a, 2**62).should == 2**62 + 7
285
+ @cache.incr(:a, 2**62).should < 2**62
286
+ end
287
+
288
+ it 'should fail if not number' do
289
+ @cache.incr(:a).should == nil
290
+ @cache.get_hash([:a]).should == {}
291
+ @cache.set(:b, :foo)
292
+ lambda {@cache.incr(:b)}.should raise_error(
293
+ AppEngine::Memcache::InvalidValueError)
294
+ end
295
+
296
+ it 'should increment strings' do
297
+ @cache.set(:a, '7')
298
+ @cache.incr(:a).should == 8
299
+ end
300
+ end
301
+
302
+ describe 'decr' do
303
+ it 'should decrement number' do
304
+ @cache.set(:a, 7)
305
+ @cache.decr(:a).should == 6
306
+ @cache.get(:a).should == 6
307
+ @cache.decr(:a, 2).should == 4
308
+ @cache.get(:a).should == 4
309
+ @cache.decr(:a, 4).should == 0
310
+ @cache.get(:a).should == 0
311
+ pending 'local decrement wrapping' do
312
+ @cache.decr(:a, 6).should == 0
313
+ @cache.get(:a).should == 0
314
+ end
315
+ end
316
+ end
317
+
318
+ describe 'readonly' do
319
+ before :each do
320
+ @rocache = AppEngine::Memcache.new(:readonly => true)
321
+ @ex = AppEngine::Memcache::MemcacheError
322
+ end
323
+
324
+ it 'should allow reads' do
325
+ @cache[:a] = 1
326
+ @rocache.get(:a).should == 1
327
+ end
328
+
329
+ it 'should allow stats' do
330
+ @cache[:a] = 7
331
+ @rocache.stats[:items].should == 1
332
+ end
333
+
334
+ it 'should block delete' do
335
+ @cache[:a] = 1
336
+ lambda {@rocache.delete(:a)}.should raise_error(@ex)
337
+ @cache[:a].should == 1
338
+ end
339
+
340
+ it 'should block add' do
341
+ lambda {@rocache.add(:a, 1)}.should raise_error(@ex)
342
+ lambda {@rocache.add_many({:b => 1})}.should raise_error(@ex)
343
+ @rocache.get_hash(:a, :b).should == {}
344
+ end
345
+
346
+ it 'should block set' do
347
+ lambda {@rocache.set(:a, 1)}.should raise_error(@ex)
348
+ lambda {@rocache.set_many({:b => 1})}.should raise_error(@ex)
349
+ @rocache.get_hash(:a, :b).should == {}
350
+ end
351
+
352
+ it 'should block replace' do
353
+ @cache[:a, :b] = :A, :B
354
+ lambda {@rocache.replace(:a, 1)}.should raise_error(@ex)
355
+ lambda {@rocache.replace_many({:b => 1})}.should raise_error(@ex)
356
+ @rocache[:a, :b].should == [:A, :B]
357
+ end
358
+
359
+ it 'should block clear' do
360
+ @cache[:a] = 1
361
+ lambda {@rocache.clear}.should raise_error(@ex)
362
+ lambda {@rocache.flush_all}.should raise_error(@ex)
363
+ @rocache[:a].should == 1
364
+ end
365
+
366
+ it 'should block incr' do
367
+ @cache[:a] = 1
368
+ lambda {@rocache.incr(:a)}.should raise_error(@ex)
369
+ @rocache[:a].should == 1
370
+ end
371
+
372
+ it 'should block decr' do
373
+ @cache[:a] = 1
374
+ lambda {@rocache.decr(:a)}.should raise_error(@ex)
375
+ @rocache[:a].should == 1
376
+ end
377
+ end
378
+
379
+ describe "namespaces" do
380
+ it 'should get namespace from initialize' do
381
+ cache = AppEngine::Memcache.new(:namespace => "foo")
382
+ cache.namespace.should == "foo"
383
+ end
384
+ end
385
+ end