simply_stored 0.3.6 → 0.3.7

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.
@@ -0,0 +1,240 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchTest < Test::Unit::TestCase
5
+ context "A simply stored couch instance" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ end
10
+
11
+ context "design documents" do
12
+ should "delete all" do
13
+ db = "http://127.0.0.1:5984/#{CouchPotato::Config.database_name}"
14
+ assert_equal 0, SimplyStored::Couch.delete_all_design_documents(db)
15
+ user = User.create
16
+ Post.create(:user => user)
17
+ user.posts
18
+ assert_equal 1, SimplyStored::Couch.delete_all_design_documents(db)
19
+ end
20
+ end
21
+
22
+ context "when creating instances" do
23
+ should "populate the attributes" do
24
+ user = User.create(:title => "Mr.", :name => "Host Master")
25
+ assert_equal "Mr.", user.title
26
+ assert_equal "Host Master", user.name
27
+ end
28
+
29
+ should "save the instance" do
30
+ user = User.create(:title => "Mr.")
31
+ assert !user.new_record?
32
+ end
33
+
34
+ context "with a bang" do
35
+ should 'not raise an exception when saving succeeded' do
36
+ assert_nothing_raised do
37
+ User.create!(:title => "Mr.")
38
+ end
39
+ end
40
+
41
+ should 'save the user' do
42
+ user = User.create!(:title => "Mr.")
43
+ assert !user.new_record?
44
+ end
45
+
46
+ should 'raise an error when the validations failed' do
47
+ assert_raises(CouchPotato::Database::ValidationsFailedError) do
48
+ User.create!(:title => nil)
49
+ end
50
+ end
51
+ end
52
+
53
+ context "with a block" do
54
+ should 'call the block with the record' do
55
+ user = User.create do |u|
56
+ u.title = "Mr."
57
+ end
58
+
59
+ assert_equal "Mr.", user.title
60
+ end
61
+
62
+ should 'save the record' do
63
+ user = User.create do |u|
64
+ u.title = "Mr."
65
+ end
66
+ assert !user.new_record?
67
+ end
68
+
69
+ should 'assign attributes via the hash' do
70
+ user = User.create(:title => "Mr.") do |u|
71
+ u.name = "Host Master"
72
+ end
73
+
74
+ assert_equal "Mr.", user.title
75
+ assert_equal "Host Master", user.name
76
+ end
77
+ end
78
+ end
79
+
80
+ context "when saving an instance" do
81
+ should "um, save the instance" do
82
+ user = User.new(:title => "Mr.")
83
+ assert user.new_record?
84
+ user.save
85
+ assert !user.new_record?
86
+ end
87
+
88
+ context "when using save!" do
89
+ should 'raise an exception when a validation isnt fulfilled' do
90
+ user = User.new
91
+ assert_raises(CouchPotato::Database::ValidationsFailedError) do
92
+ user.save!
93
+ end
94
+ end
95
+ end
96
+
97
+ context "when using save(false)" do
98
+ should "not run the validations" do
99
+ user = User.new
100
+ user.save(false)
101
+ assert !user.new?
102
+ assert !user.dirty?
103
+ end
104
+ end
105
+ end
106
+
107
+ context "when destroying an instance" do
108
+ should "remove the instance" do
109
+ user = User.create(:title => "Mr")
110
+ assert_difference 'User.find(:all).size', -1 do
111
+ user.destroy
112
+ end
113
+ end
114
+
115
+ should 'return the frozen instance, brrrr' do
116
+ user = User.create(:title => "Mr")
117
+ assert_equal user, user.destroy
118
+ end
119
+ end
120
+
121
+ context "when updating attributes" do
122
+ should "merge in the updated attributes" do
123
+ user = User.create(:title => "Mr.")
124
+ user.update_attributes(:title => "Mrs.")
125
+ assert_equal "Mrs.", user.title
126
+ end
127
+
128
+ should "save the instance" do
129
+ user = User.create(:title => "Mr.")
130
+ user.update_attributes(:title => "Mrs.")
131
+ assert !user.dirty?
132
+ end
133
+ end
134
+
135
+
136
+ context "when counting" do
137
+ setup do
138
+ recreate_db
139
+ end
140
+
141
+ context "when counting all" do
142
+ should "return the number of objects in the database" do
143
+ CountMe.create(:title => "Mr.")
144
+ CountMe.create(:title => "Mrs.")
145
+ assert_equal 2, CountMe.find(:all).size
146
+ assert_equal 2, CountMe.count
147
+ end
148
+
149
+ should "only count the correct class" do
150
+ CountMe.create(:title => "Mr.")
151
+ DontCountMe.create(:title => 'Foo')
152
+ assert_equal 1, CountMe.find(:all).size
153
+ assert_equal 1, CountMe.count
154
+ end
155
+ end
156
+
157
+ context "when counting by prefix" do
158
+ should "return the number of matching objects" do
159
+ CountMe.create(:title => "Mr.")
160
+ CountMe.create(:title => "Mrs.")
161
+ assert_equal 1, CountMe.find_all_by_title('Mr.').size
162
+ assert_equal 1, CountMe.count_by_title('Mr.')
163
+ end
164
+
165
+ should "only count the correct class" do
166
+ CountMe.create(:title => "Mr.")
167
+ DontCountMe.create(:title => 'Mr.')
168
+ assert_equal 1, CountMe.find_all_by_title('Mr.').size
169
+ assert_equal 1, CountMe.count_by_title('Mr.')
170
+ end
171
+ end
172
+ end
173
+
174
+ context "when reloading an instance" do
175
+ should "reload new attributes from the database" do
176
+ user = User.create(:title => "Mr.", :name => "Host Master")
177
+ user2 = User.find(user.id)
178
+ user2.update_attributes(:title => "Mrs.", :name => "Hostess Masteress")
179
+ user.reload
180
+ assert_equal "Mrs.", user.title
181
+ assert_equal "Hostess Masteress", user.name
182
+ end
183
+
184
+ should "remove attributes that are no longer in the database" do
185
+ user = User.create(:title => "Mr.", :name => "Host Master")
186
+ assert_not_nil user.name
187
+ same_user_in_different_thread = User.find(user.id)
188
+ same_user_in_different_thread.name = nil
189
+ same_user_in_different_thread.save!
190
+ assert_nil user.reload.name
191
+ end
192
+
193
+ should "also remove foreign key attributes that are no longer in the database" do
194
+ user = User.create(:title => "Mr.", :name => "Host Master")
195
+ post = Post.create(:user => user)
196
+ assert_not_nil post.user_id
197
+ same_post_in_different_thread = Post.find(post.id)
198
+ same_post_in_different_thread.user = nil
199
+ same_post_in_different_thread.save!
200
+ assert_nil post.reload.user_id
201
+ end
202
+
203
+ should "not be dirty after reloading" do
204
+ user = User.create(:title => "Mr.", :name => "Host Master")
205
+ user2 = User.find(user.id)
206
+ user2.update_attributes(:title => "Mrs.", :name => "Hostess Masteress")
207
+ user.reload
208
+ assert !user.dirty?
209
+ end
210
+
211
+ should "ensure that association caches for has_many are cleared" do
212
+ user = User.create(:title => "Mr.", :name => "Host Master")
213
+ post = Post.create(:user => user)
214
+ assert_equal 1, user.posts.size
215
+ assert_not_nil user.instance_variable_get("@posts")
216
+ user.reload
217
+ assert_nil user.instance_variable_get("@posts")
218
+ assert_not_nil user.posts.first
219
+ end
220
+
221
+ should "ensure that association caches for belongs_to are cleared" do
222
+ user = User.create(:title => "Mr.", :name => "Host Master")
223
+ post = Post.create(:user => user)
224
+ post.user
225
+ assert_not_nil post.instance_variable_get("@user")
226
+ post.reload
227
+ assert_nil post.instance_variable_get("@user")
228
+ assert_not_nil post.user
229
+ end
230
+
231
+ should "update the revision" do
232
+ user = User.create(:title => "Mr.", :name => "Host Master")
233
+ user2 = User.find(user.id)
234
+ user2.update_attributes(:title => "Mrs.", :name => "Hostess Masteress")
235
+ user.reload
236
+ assert_equal user._rev, user2._rev
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchMassAssignmentProtectionTest < Test::Unit::TestCase
5
+ context "attribute proctection against mass assignment" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ end
10
+
11
+ context "when using attr_protected" do
12
+ setup do
13
+ Category.instance_eval do
14
+ @_accessible_attributes = []
15
+ attr_protected :parent, :alias
16
+ end
17
+ end
18
+
19
+ should "not allow to set with mass assignment using attributes=" do
20
+ item = Category.new
21
+ item.attributes = {:parent => 'a', :name => 'c'}
22
+ assert_equal 'c', item.name
23
+ assert_nil item.parent
24
+ end
25
+
26
+ should "not allow to set with mass assignment using attributes= - ignore string vs. symbol" do
27
+ item = Category.new
28
+ item.attributes = {'parent' => 'a', 'name' => 'c'}
29
+ assert_equal 'c', item.name
30
+ assert_nil item.parent
31
+ end
32
+
33
+ should "not allow to set with mass assignment using the constructor" do
34
+ item = Category.new(:parent => 'a', :name => 'c')
35
+ assert_equal 'c', item.name
36
+ assert_nil item.parent
37
+ end
38
+
39
+ should "not allow to set with mass assignment using update_attributes" do
40
+ item = Category.new
41
+ item.update_attributes(:parent => 'a', :name => 'c')
42
+ assert_equal 'c', item.name
43
+ assert_nil item.parent
44
+ end
45
+ end
46
+
47
+ context "attr_accessible" do
48
+ setup do
49
+ Category.instance_eval do
50
+ @_protected_attributes = []
51
+ attr_accessible :name
52
+ end
53
+ end
54
+
55
+ should "not allow to set with mass assignment using attributes=" do
56
+ item = Category.new
57
+ item.attributes = {:parent => 'a', :name => 'c'}
58
+ assert_equal 'c', item.name
59
+ assert_nil item.parent
60
+ end
61
+
62
+ should "not allow to set with mass assignment using the constructor" do
63
+ item = Category.new(:parent => 'a', :name => 'c')
64
+ assert_equal 'c', item.name
65
+ assert_nil item.parent
66
+ end
67
+
68
+ should "not allow to set with mass assignment using update_attributes" do
69
+ item = Category.new
70
+ item.update_attributes(:parent => 'a', :name => 'c')
71
+ # item.reload
72
+ assert_equal 'c', item.name
73
+ assert_nil item.parent
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,256 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../fixtures/couch')
3
+
4
+ class CouchS3Test < Test::Unit::TestCase
5
+ context "with s3 interaction" do
6
+ setup do
7
+ CouchPotato::Config.database_name = 'simply_stored_test'
8
+ recreate_db
9
+ CouchLogItem.instance_variable_set(:@_s3_connection, nil)
10
+ CouchLogItem._s3_options[:log_data][:ca_file] = nil
11
+
12
+ bucket = stub(:bckt) do
13
+ stubs(:put).returns(true)
14
+ stubs(:get).returns(true)
15
+ end
16
+
17
+ @bucket = bucket
18
+
19
+ @s3 = stub(:s3) do
20
+ stubs(:bucket).returns(bucket)
21
+ end
22
+
23
+ RightAws::S3.stubs(:new).returns @s3
24
+ @log_item = CouchLogItem.new
25
+ end
26
+
27
+ context "when saving the attachment" do
28
+ should "fetch the collection" do
29
+ @log_item.log_data = "Yay! It logged!"
30
+ RightAws::S3.expects(:new).with('abcdef', 'secret!', :multi_thread => true, :ca_file => nil, :logger => nil).returns(@s3)
31
+ @log_item.save
32
+ end
33
+
34
+ should "upload the file" do
35
+ @log_item.log_data = "Yay! It logged!"
36
+ @bucket.expects(:put).with(anything, "Yay! It logged!", {}, anything)
37
+ @log_item.save
38
+ end
39
+
40
+ should "also upload on save!" do
41
+ @log_item.log_data = "Yay! It logged!"
42
+ @bucket.expects(:put).with(anything, "Yay! It logged!", {}, anything)
43
+ @log_item.save!
44
+ end
45
+
46
+ should "use the specified bucket" do
47
+ @log_item.log_data = "Yay! It logged!"
48
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
49
+ @s3.expects(:bucket).with('mybucket').returns(@bucket)
50
+ @log_item.save
51
+ end
52
+
53
+ should "create the bucket if it doesn't exist" do
54
+ @log_item.log_data = "Yay! log me"
55
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
56
+
57
+ @s3.expects(:bucket).with('mybucket').returns(nil)
58
+ @s3.expects(:bucket).with('mybucket', true, 'private', :location => nil).returns(@bucket)
59
+ @log_item.save
60
+ end
61
+
62
+ should "accept :us location option but not set it in RightAWS::S3" do
63
+ @log_item.log_data = "Yay! log me"
64
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
65
+ CouchLogItem._s3_options[:log_data][:location] = :us
66
+
67
+ @s3.expects(:bucket).with('mybucket').returns(nil)
68
+ @s3.expects(:bucket).with('mybucket', true, 'private', :location => nil).returns(@bucket)
69
+ @log_item.save
70
+ end
71
+
72
+ should "raise an error if the bucket is not ours" do
73
+ @log_item.log_data = "Yay! log me too"
74
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
75
+ CouchLogItem._s3_options[:log_data][:location] = :eu
76
+
77
+ @s3.expects(:bucket).with('mybucket').returns(nil)
78
+ @s3.expects(:bucket).with('mybucket', true, 'private', :location => :eu).raises(RightAws::AwsError, 'BucketAlreadyExists: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again')
79
+
80
+ assert_raise(ArgumentError) do
81
+ @log_item.save
82
+ end
83
+ end
84
+
85
+ should "pass the logger object down to RightAws" do
86
+ logger = mock()
87
+ @log_item.log_data = "Yay! log me"
88
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
89
+ CouchLogItem._s3_options[:log_data][:logger] = logger
90
+
91
+ RightAws::S3.expects(:new).with(anything, anything, {:logger => logger, :ca_file => nil, :multi_thread => true}).returns(@s3)
92
+ @log_item.save
93
+ end
94
+
95
+ should "not upload the attachment when it hasn't been changed" do
96
+ @bucket.expects(:put).never
97
+ @log_item.save
98
+ end
99
+
100
+ should "set the permissions to private by default" do
101
+ class Item
102
+ include SimplyStored::Couch
103
+ has_s3_attachment :log_data, :bucket => 'mybucket'
104
+ end
105
+ @bucket.expects(:put).with(anything, anything, {}, 'private')
106
+ @log_item = Item.new
107
+ @log_item.log_data = 'Yay!'
108
+ @log_item.save
109
+ end
110
+
111
+ should "set the permissions to whatever's specified in the options for the attachment" do
112
+ @log_item.save
113
+ old_perms = CouchLogItem._s3_options[:log_data][:permissions]
114
+ CouchLogItem._s3_options[:log_data][:permissions] = 'public-read'
115
+ @bucket.expects(:put).with(anything, anything, {}, 'public-read')
116
+ @log_item.log_data = 'Yay!'
117
+ @log_item.save
118
+ CouchLogItem._s3_options[:log_data][:permissions] = old_perms
119
+ end
120
+
121
+ should "use the full class name and the id as key" do
122
+ @log_item.save
123
+ @bucket.expects(:put).with("couch_log_items/log_data/#{@log_item.id}", 'Yay!', {}, anything)
124
+ @log_item.log_data = 'Yay!'
125
+ @log_item.save
126
+ end
127
+
128
+ should "mark the attachment as not dirty after uploading" do
129
+ @log_item.log_data = 'Yay!'
130
+ @log_item.save
131
+ assert !@log_item.instance_variable_get(:@_s3_attachments)[:log_data][:dirty]
132
+ end
133
+
134
+ should 'store the attachment when the validations succeeded' do
135
+ @log_item.log_data = 'Yay!'
136
+ @log_item.stubs(:valid?).returns(true)
137
+ @bucket.expects(:put)
138
+ @log_item.save
139
+ end
140
+
141
+ should "not store the attachment when the validations failed" do
142
+ @log_item.log_data = 'Yay!'
143
+ @log_item.stubs(:valid?).returns(false)
144
+ @bucket.expects(:put).never
145
+ @log_item.save
146
+ end
147
+
148
+ should "save the attachment status" do
149
+ @log_item.save
150
+ @log_item.attributes["log_data_attachments"]
151
+ end
152
+
153
+ should "save generate the url for the attachment" do
154
+ @log_item._s3_options[:log_data][:bucket] = 'bucket-for-monsieur'
155
+ @log_item._s3_options[:log_data][:permissions] = 'public-read'
156
+ @log_item.save
157
+ assert_equal "http://bucket-for-monsieur.s3.amazonaws.com/#{@log_item.s3_attachment_key(:log_data)}", @log_item.log_data_url
158
+ end
159
+
160
+ should "add a short-lived access key for private attachments" do
161
+ @log_item._s3_options[:log_data][:bucket] = 'bucket-for-monsieur'
162
+ @log_item._s3_options[:log_data][:location] = :us
163
+ @log_item._s3_options[:log_data][:permissions] = 'private'
164
+ @log_item.save
165
+ assert @log_item.log_data_url.gsub("%2F", '/').include?("https://bucket-for-monsieur.s3.amazonaws.com:443/#{@log_item.s3_attachment_key(:log_data)}"), @log_item.log_data_url
166
+ assert @log_item.log_data_url.include?("Signature=")
167
+ assert @log_item.log_data_url.include?("Expires=")
168
+ end
169
+
170
+ should "serialize data other than strings to json" do
171
+ @log_item.log_data = ['one log entry', 'and another one']
172
+ @bucket.expects(:put).with(anything, '["one log entry","and another one"]', {}, anything)
173
+ @log_item.save
174
+ end
175
+
176
+ context "when noting the size of the attachment" do
177
+ should "store on upload" do
178
+ @log_item.log_data = 'abc'
179
+ @bucket.expects(:put)
180
+ assert @log_item.save
181
+ assert_equal 3, @log_item.log_data_size
182
+ end
183
+
184
+ should "update the size if the attachment gets updated" do
185
+ @log_item.log_data = 'abc'
186
+ @bucket.stubs(:put)
187
+ assert @log_item.save
188
+ assert_equal 3, @log_item.log_data_size
189
+
190
+ @log_item.log_data = 'example'
191
+ assert @log_item.save
192
+ assert_equal 7, @log_item.log_data_size
193
+ end
194
+
195
+ should "store the size of json attachments" do
196
+ @log_item.log_data = ['abc']
197
+ @bucket.stubs(:put)
198
+ assert @log_item.save
199
+ assert_equal ['abc'].to_json.size, @log_item.log_data_size
200
+ end
201
+ end
202
+ end
203
+
204
+ context "when fetching the data" do
205
+ should "create a configured S3 connection" do
206
+ CouchLogItem._s3_options[:log_data][:bucket] = 'mybucket'
207
+ CouchLogItem._s3_options[:log_data][:location] = :eu
208
+ CouchLogItem._s3_options[:log_data][:ca_file] = '/etc/ssl/ca.crt'
209
+
210
+ RightAws::S3.expects(:new).with('abcdef', 'secret!', :multi_thread => true, :ca_file => '/etc/ssl/ca.crt', :logger => nil).returns(@s3)
211
+
212
+ @log_item.log_data
213
+ end
214
+
215
+ should "fetch the data from s3 and set the attachment attribute" do
216
+ @log_item.instance_variable_set(:@_s3_attachments, {})
217
+ @bucket.expects(:get).with("couch_log_items/log_data/#{@log_item.id}").returns("Yay!")
218
+ assert_equal "Yay!", @log_item.log_data
219
+ end
220
+
221
+ should "not mark the the attachment as dirty" do
222
+ @log_item.instance_variable_set(:@_s3_attachments, {})
223
+ @bucket.expects(:get).with("couch_log_items/log_data/#{@log_item.id}").returns("Yay!")
224
+ @log_item.log_data
225
+ assert !@log_item._s3_attachments[:log_data][:dirty]
226
+ end
227
+
228
+ should "not try to fetch the attachment if the value is already set" do
229
+ @log_item.log_data = "Yay!"
230
+ @bucket.expects(:get).never
231
+ assert_equal "Yay!", @log_item.log_data
232
+ end
233
+ end
234
+
235
+ context "when deleting" do
236
+ setup do
237
+ CouchLogItem._s3_options[:log_data][:after_delete] = :nothing
238
+ @log_item.log_data = 'Yatzzee'
239
+ @log_item.save
240
+ end
241
+
242
+ should "do nothing to S3" do
243
+ @bucket.expects(:key).never
244
+ @log_item.delete
245
+ end
246
+
247
+ should "also delete on S3 if configured so" do
248
+ CouchLogItem._s3_options[:log_data][:after_delete] = :delete
249
+ s3_key = mock(:delete => true)
250
+ @bucket.expects(:key).with(@log_item.s3_attachment_key('log_data'), true).returns(s3_key)
251
+ @log_item.delete
252
+ end
253
+
254
+ end
255
+ end
256
+ end