dm-parse 0.1.0

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,98 @@
1
+ require "spec_helper"
2
+
3
+ describe "resource" do
4
+ subject { resource }
5
+
6
+ let(:resource) { model.new title: "Test Title", rank: 3, body: "Test Body" }
7
+ let(:model) { Article }
8
+
9
+ before { model.all.destroy }
10
+
11
+ it { should be_new }
12
+ it { should be_dirty }
13
+ its(:id) { should be_nil }
14
+ its(:created_at) { should be_nil }
15
+ its(:updated_at) { should be_nil }
16
+
17
+ context "after save" do
18
+ before { resource.save }
19
+
20
+ it { should_not be_nil }
21
+ it { should_not be_dirty }
22
+ its(:id) { should_not be_nil }
23
+ its(:created_at) { should_not be_nil }
24
+ its(:updated_at) { should_not be_nil }
25
+ end
26
+ end
27
+
28
+ describe "collection" do
29
+ subject { collection }
30
+
31
+ let(:model) { Article }
32
+ let(:collection) { model.all(:rank.gte => 5, :closed_at.gt => 1.day.from_now, :closed_at.lt => 3.days.from_now, :comments => { :body => /aa/im }) }
33
+
34
+ before { model.all.destroy }
35
+ before { Comment.all.destroy }
36
+
37
+ its(:size) { should eq(0) }
38
+
39
+ context "when resource in scope is saved" do
40
+ before do
41
+ resource = model.create! rank: 5, closed_at: 2.day.from_now
42
+ resource.comments.create body: "AA"
43
+ end
44
+
45
+ its(:size) { should eq(1) }
46
+ its(:count) { should eq(1) }
47
+ end
48
+
49
+ context "when resource out of scope is saved" do
50
+ before { model.create rank: 4 }
51
+
52
+ its(:size) { should eq(0) }
53
+ its(:count) { should eq(0) }
54
+ end
55
+ end
56
+
57
+ describe User do
58
+ subject { user }
59
+
60
+ before do
61
+ repository :master do
62
+ model.all.destroy
63
+ end
64
+ end
65
+
66
+ let(:model) { described_class }
67
+ let(:username) { "testuser0" }
68
+ let(:password) { "abcdefgh" }
69
+ let(:user) { model.new username: username, password: password }
70
+
71
+ it { should be_valid }
72
+
73
+ context "when a vaid email is given" do
74
+ let(:user) { model.new username: username, password: password, email: "#{username}@abc.com" }
75
+
76
+ it { should be_valid }
77
+ end
78
+
79
+ context "when an invalid email is given" do
80
+ let(:user) { model.new username: username, password: password, email: "dafdjlfdsaj" }
81
+
82
+ it { should_not be_valid }
83
+ end
84
+
85
+ describe "class" do
86
+ subject { model }
87
+
88
+ let(:user) { model.create! username: username, password: password }
89
+
90
+ its(:storage_name) { should eq("_User") }
91
+
92
+ describe "#authenticate" do
93
+ subject { model.authenticate username, password }
94
+
95
+ it { should eq(user) }
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,302 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Adapters::ParseAdapter do
4
+ let(:adapter) { DataMapper.setup(:default, options) }
5
+ let(:options) { { adapter: :parse, app_id: app_id, api_key: api_key } }
6
+ let(:app_id) { "xxx" }
7
+ let(:api_key) { "yyy" }
8
+ let(:app_id_header) { "X-Parse-Application-Id" }
9
+ let(:api_key_header) { "X-Parse-REST-API-Key" }
10
+ let(:master_key_header) { "X-Parse-Master-Key" }
11
+ let(:model) { Article }
12
+
13
+ describe "#parse_conditions_for" do
14
+ subject { adapter.send :parse_conditions_for, query }
15
+
16
+ context "when query is nil" do
17
+ let(:query) { model.all.query }
18
+ it { should be_nil }
19
+ end
20
+
21
+ context "when query assigns some exact values" do
22
+ let(:query) { model.all(:id => "z", :title => "x", :body => "y").query }
23
+ it { should eq("objectId" => "z", "title" => "x", "body" => "y") }
24
+ end
25
+
26
+ [:gt, :gte, :lt, :lte].each do |slug|
27
+ context "when query has #{slug} comparison" do
28
+ let(:query) { model.all(:id => "z", :rank.send(slug) => 5).query }
29
+ it { should eq("objectId" => "z", "rank" => {"$#{slug}" => 5}) }
30
+ end
31
+ end
32
+
33
+ context "when query has :not operation" do
34
+ let(:query) { model.all(:rank.not => 5, :body.not => "x").query }
35
+ it { should eq("rank" => {"$ne" => 5}, "body" => {"$ne" => "x"}) }
36
+ end
37
+
38
+ context "when query has multiple comparisons of one field" do
39
+ let(:query) { model.all(:rank.lt => 5, :rank.gt => 3).query }
40
+ it { should eq("rank" => {"$lt" => 5, "$gt" => 3}) }
41
+ end
42
+
43
+ context "when query has :in comparison" do
44
+ let(:query) { model.all("rank" => 1..3).query }
45
+ it { should eq("rank" => {"$in" => (1..3).to_a}) }
46
+ end
47
+
48
+ context "when query has :regexp comparison" do
49
+ let(:query) { model.all(:body => regex).query }
50
+ let(:regex) { /^[A-Z]\d/ }
51
+
52
+ it { should eq("body" => {"$regex" => "^[A-Z]\\d"}) }
53
+
54
+ context "when regular expersion has options" do
55
+ let(:regex) { /bbq/mi }
56
+
57
+ it { should eq("body" => {"$regex"=>"bbq", "$options"=>"im"}) }
58
+ end
59
+ end
60
+
61
+ context "when query has :or operation" do
62
+ let(:query) { (model.all(:rank => 3) + model.all(:rank => 4) + model.all(:rank => 5)).query }
63
+ it { should eq("$or" => [{"rank" => 3}, {"rank" => 4}, {"rank" => 5}]) }
64
+ end
65
+
66
+ context "when query has union operator" do
67
+ let(:query) { (model.all(:rank => 3) | model.all(:rank => 4) | model.all(:rank => 5)).query }
68
+ it { should eq("$or" => [{"rank" => 3}, {"rank" => 4}, {"rank" => 5}]) }
69
+ end
70
+
71
+ context "when query has and operator" do
72
+ let(:query) { (model.all("rank" => 5) & model.all("body" => "x")).query }
73
+ it { should eq("rank" => 5, "body" => "x") }
74
+ end
75
+
76
+ context "when query has complex :not operation" do
77
+ let(:query) { (model.all - model.all(:rank => 5, :body => "x")).query }
78
+ it { should eq("rank" => {"$ne" => 5}, "body" => {"$ne" => "x"}) }
79
+
80
+ context "when condition is not EqualToComparison" do
81
+ let(:query) { model.all(:rank.not => [2, 3]).query }
82
+ it { should eq("rank" => {"$nin" => [2, 3]}) }
83
+ end
84
+ end
85
+
86
+ describe "exceptions" do
87
+ subject { -> { adapter.send :parse_conditions_for, query } }
88
+
89
+ context "when the key is same" do
90
+ let(:query) { (model.all("rank" => 5) & model.all("rank" => 3)).query }
91
+ it { should raise_error("can only use one EqualToComparison for a field") }
92
+ end
93
+
94
+ context "when query has :eql and others of one field" do
95
+ let(:query) { model.all(:rank => 5, :rank.gt => 3).query }
96
+ it { should raise_error }
97
+ end
98
+ end # exceptions
99
+ end # #parse_conditions_for
100
+
101
+ describe "#parse_orders_for" do
102
+ subject { adapter.send :parse_orders_for, query }
103
+ let(:query) { model.all("body" => "x").query }
104
+ it { should be_nil }
105
+
106
+ context "when orders are given" do
107
+ let(:query) { model.all(:order => [:rank.asc, :title.desc]).query }
108
+ it { should eq("rank,-title") }
109
+ end
110
+ end # #parse_orders_for
111
+
112
+ describe "#parse_limit_for" do
113
+ subject { adapter.send :parse_limit_for, query }
114
+ let(:query) { model.all.query }
115
+ it { should eq(1000) }
116
+
117
+ context "when 0 is given" do
118
+ let(:query) { model.all(:limit => 0).query }
119
+ it { should eq(0) }
120
+ end
121
+
122
+ describe "exceptions" do
123
+ subject { -> { adapter.send :parse_limit_for, query } }
124
+
125
+ context "when 1001 is given" do
126
+ let(:query) { model.all(:limit => 1001).query }
127
+ it { should raise_error("Parse limit: only number from 0 to 1000 is valid") }
128
+ end
129
+ end
130
+ end # #parse_limit_for
131
+
132
+ describe "#parse_offset_for" do
133
+ subject { adapter.send :parse_offset_for, query }
134
+ let(:query) { model.all.query }
135
+ it { should eq(0) }
136
+
137
+ context "when a number is given" do
138
+ let(:query) { model.all(:offset => number, :limit => 200).query }
139
+
140
+ context "the number is positive" do
141
+ let(:number) { 1 }
142
+ it { should eq(number) }
143
+ end
144
+
145
+ context "the number is positive" do
146
+ let(:number) { 0 }
147
+ it { should eq(number) }
148
+ end
149
+ end
150
+
151
+ describe "exceptions" do
152
+ subject { -> { adapter.send :parse_offset_for, query } }
153
+ let(:query) { model.all(:offset => number, :limit => 200).query }
154
+
155
+ context "the number is negative" do
156
+ let(:number) { -1 }
157
+ it { should raise_error }
158
+ end
159
+ end
160
+ end # #parse_offset_for
161
+
162
+ describe "#parse_params_for" do
163
+ subject { adapter.send :parse_params_for, query }
164
+ let(:query) { model.all.query }
165
+ it { should eq(:limit => 1000) }
166
+
167
+ context "when limit is given" do
168
+ let(:query) { model.all(:limit => 200).query }
169
+ it { should eq(:limit => 200) }
170
+ end
171
+
172
+ context "when conditions is given" do
173
+ let(:query) { model.all(:rank => 5).query }
174
+ it { should eq(:limit => 1000, :where => {"rank" => 5}.to_json) }
175
+ end
176
+
177
+ context "when offset is given" do
178
+ let(:query) { model.all(:limit => 200, :offset => 300).query }
179
+ it { should eq(:limit => 200, :skip => 300) }
180
+ end
181
+
182
+ context "when orders are given" do
183
+ let(:query) { model.all(:order => [:rank.desc]).query }
184
+ it { should eq(:limit => 1000, :order => "-rank") }
185
+ end
186
+ end # #parse_params_for
187
+
188
+ shared_examples_for DataMapper::Parse::Resource do
189
+ let(:options) { { adapter: :parse, app_id: app_id, api_key: api_key} }
190
+
191
+ it { should be_a(DataMapper::Parse::Resource) }
192
+ its(:options) { should eq(format: :json, headers: {app_id_header => app_id, api_key_header => api_key}) }
193
+ context "when master mode is on" do
194
+ let(:options) { { adapter: :parse, app_id: app_id, api_key: api_key, master: true } }
195
+
196
+ its(:options) { should eq(format: :json, headers: {app_id_header => app_id, master_key_header => api_key}) }
197
+ end
198
+ end
199
+
200
+ describe "#classes" do
201
+ subject { adapter.classes }
202
+ its(:url) { should eq("https://api.parse.com/1/classes") }
203
+ it_should_behave_like DataMapper::Parse::Resource
204
+ end
205
+
206
+ describe "#users" do
207
+ subject { adapter.users }
208
+ its(:url) { should eq("https://api.parse.com/1/users") }
209
+ it_should_behave_like DataMapper::Parse::Resource
210
+ end
211
+
212
+ describe "#login" do
213
+ subject { adapter.login }
214
+ its(:url) { should eq("https://api.parse.com/1/login") }
215
+ it_should_behave_like DataMapper::Parse::Resource
216
+ end
217
+
218
+ describe "#password_reset" do
219
+ subject { adapter.password_reset }
220
+ its(:url) { should eq("https://api.parse.com/1/requestPasswordReset") }
221
+ it_should_behave_like DataMapper::Parse::Resource
222
+ end
223
+
224
+ describe "#parse_resources_for" do
225
+ subject { adapter.parse_resources_for model }
226
+ it { should eq(adapter.classes[model.storage_name]) }
227
+
228
+ context "when storage_name of model is _User" do
229
+ before(:each) { model.stub(storage_name: "_User") }
230
+ it { should eq(adapter.users) }
231
+ end
232
+ end
233
+
234
+ describe "#parse_resource_for" do
235
+ subject { adapter.parse_resource_for resource }
236
+ let(:resource) { model.new id: "xxx" }
237
+ it { should eq(adapter.parse_resources_for(model)["xxx"]) }
238
+ end
239
+
240
+ describe "#create" do
241
+ subject { adapter.create resources }
242
+
243
+ let(:resources) { [resource] }
244
+ let(:resource) { model.new attributes }
245
+ let(:attributes) { { id: "fd", rank: 3, created_at: 1.day.ago, updated_at: 2.days.ago } }
246
+
247
+ before(:each) do
248
+ double_resources = double("resource")
249
+ double_resources.should_receive(:post).with(params: {"rank" => 3}).once.and_return({"createdAt" => "2011-08-20T02:06:57.931Z", "objectId" => "Ed1nuqPvcm"})
250
+ adapter.stub(parse_resources_for: double_resources)
251
+ end
252
+
253
+ it { should eq(1) }
254
+ end
255
+
256
+ describe "#read" do
257
+ subject { adapter.read query }
258
+
259
+ let(:query) { model.all(:rank => 4).query }
260
+ let(:results) { [{"objectId" => "anything"}] }
261
+
262
+ before(:each) do
263
+ double_resources = double("resource")
264
+ double_resources.should_receive(:get).with(params: { limit: 1000, where: {"rank" => 4}.to_json }).once.and_return("results" => results)
265
+ adapter.stub(parse_resources_for: double_resources)
266
+ end
267
+
268
+ it { should eq(results) }
269
+ end
270
+
271
+ describe "#delete" do
272
+ subject { adapter.delete resources }
273
+
274
+ let(:resources) { [resource] }
275
+ let(:resource) { model.new id: id }
276
+ let(:id) { "xxx" }
277
+
278
+ before(:each) do
279
+ double_resource = double("resource")
280
+ double_resource.should_receive(:delete).with(no_args).once.and_return({})
281
+ adapter.stub(parse_resource_for: double_resource)
282
+ end
283
+
284
+ it { should eq(1) }
285
+ end
286
+
287
+ describe "#update" do
288
+ subject { adapter.update attributes, resources }
289
+
290
+ let(:resources) { [resource] }
291
+ let(:resource) { model.new id: "xxx" }
292
+ let(:attributes) { model.new(rank: 5, created_at: 1.day.ago, updated_at: 2.days.ago).attributes(:property) }
293
+
294
+ before(:each) do
295
+ double_resource = double("resource")
296
+ double_resource.should_receive(:put).with(params: {"rank" => 5}).once.and_return("updatedAt" => "2011-08-21T18:02:52.248Z")
297
+ adapter.stub(parse_resource_for: double_resource)
298
+ end
299
+
300
+ it { should eq(1) }
301
+ end
302
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Property::ParseDate do
4
+ subject { property }
5
+ let(:property) { Article.properties[:created_at] }
6
+ let(:datetime) { DateTime.parse("2011-08-21T18:02:52.249Z") }
7
+
8
+ describe "#dump" do
9
+ subject { property.dump value }
10
+ let(:value) { datetime }
11
+
12
+ it { should eq("__type" => "Date", "iso" => datetime.utc.iso8601(3)) }
13
+
14
+ context "when value is nil" do
15
+ let(:value) { nil }
16
+
17
+ it { should be_nil }
18
+ end
19
+ end
20
+
21
+ describe "#load" do
22
+ subject { property.load value }
23
+ let(:value) { {"__type" => "Date", "iso" => datetime.utc.iso8601(3)} }
24
+
25
+ it { should eq(datetime) }
26
+
27
+ context "when value is in string" do
28
+ let(:value) { "2011-08-21T18:02:52.249Z" }
29
+
30
+ it { should eq(datetime) }
31
+ end
32
+
33
+ context "when value is nil" do
34
+ let(:value) { nil }
35
+
36
+ it { should be_nil }
37
+ end
38
+ end
39
+
40
+ describe "#valid?" do
41
+ subject { property.valid? datetime }
42
+
43
+ it { should be_true }
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Property::ParseKey do
4
+ subject { property }
5
+ let(:property) { Article.properties[:id] }
6
+
7
+ describe "#dump" do
8
+ subject { property.dump value }
9
+ let(:value) { "xxx" }
10
+
11
+ it { should eq("xxx") }
12
+
13
+ context "when value is nil" do
14
+ let(:value) { nil }
15
+
16
+ it { should be_nil }
17
+ end
18
+ end
19
+
20
+ describe "#load" do
21
+ subject { property.load value }
22
+ let(:value) { "xxx" }
23
+
24
+ it { should eq("xxx") }
25
+ end
26
+
27
+ describe "#valid?" do
28
+ subject { property.valid? "xxx" }
29
+
30
+ it { should be_true }
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Property::ParsePointer do
4
+ subject { property }
5
+ let(:property) { Comment.properties[:article_id] }
6
+
7
+ describe "#dump" do
8
+ subject { property.dump value }
9
+ let(:value) { "xxx" }
10
+
11
+ it { should eq("__type" => "Pointer", "className" => Article.storage_name, "objectId" => "xxx") }
12
+
13
+ context "when value is nil" do
14
+ let(:value) { nil }
15
+
16
+ it { should be_nil }
17
+ end
18
+ end
19
+
20
+ describe "#load" do
21
+ subject { property.load value }
22
+ let(:value) { {"__type" => "Pointer", "className" => Article.storage_name, "objectId" => "xxx"} }
23
+
24
+ it { should eq("xxx") }
25
+ end
26
+
27
+ describe "#valid?" do
28
+ subject { property.valid? "xxx" }
29
+
30
+ it { should be_true }
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Parse::Conditions::Regex do
4
+ let(:regex) { described_class.new value }
5
+
6
+ describe "#options" do
7
+ subject { regex.options }
8
+
9
+ context "when case insensitive option is on" do
10
+ let(:value) { /bbq/i }
11
+ it { should eq("i") }
12
+ end
13
+
14
+ context "when multiline option is on" do
15
+ let(:value) { /bbq/m }
16
+ it { should eq("m") }
17
+ end
18
+
19
+ context "when both case insensitive and multiline option is on" do
20
+ let(:value) { /bbq/mi }
21
+ it { should eq("im") }
22
+ end
23
+ end # #regex_options
24
+
25
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe DataMapper::Parse::Resource do
4
+ pending "there may be tests in the future"
5
+ end
@@ -0,0 +1,58 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'dm-parse'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
13
+
14
+ # To run the tests, setup a Parse environment in "parse_env.yml"
15
+ # under the same directory, which I don't provide.
16
+ env_file = File.join(File.dirname(__FILE__), "parse_env.yml")
17
+ settings = YAML::load(File.read env_file)
18
+ app_id = settings["app_id"]
19
+ api_key = settings["api_key"]
20
+ master_key = settings["master_key"]
21
+
22
+ raise "You must setup a parse environment before testing" unless app_id && api_key && master_key
23
+
24
+ DataMapper.setup :default, adapter: :parse, app_id: app_id, api_key: api_key
25
+ DataMapper.setup :master, adapter: :parse, app_id: app_id, api_key: master_key, master: true
26
+
27
+ class User
28
+ include DataMapper::Resource
29
+
30
+ is :parse_user
31
+ storage_names[:master] = "_User"
32
+ end
33
+
34
+ class Article
35
+ include DataMapper::Resource
36
+
37
+ is :parse
38
+
39
+ property :title, String
40
+ property :body, Text
41
+ property :rank, Integer
42
+ property :closed_at, ParseDate
43
+
44
+ has n, :comments
45
+ end
46
+
47
+ class Comment
48
+ include DataMapper::Resource
49
+
50
+ is :parse
51
+
52
+ property :body, Text
53
+
54
+ belongs_to :article
55
+ end
56
+
57
+ DataMapper.finalize
58
+