1 | var chai = require('chai'),
|
2 | spies = require('chai-spies'),
|
3 | AWS = require('aws-sdk'),
|
4 | _= require('underscore'),
|
5 | dyngo= require('../index.js');
|
6 |
|
7 | chai.use(spies);
|
8 |
|
9 | var should= chai.should(),
|
10 | assert= chai.assert;
|
11 |
|
12 | const noerr= function (done)
|
13 | {
|
14 | return function (err)
|
15 | {
|
16 | should.not.exist(err);
|
17 | done();
|
18 | };
|
19 | },
|
20 | accept= function (code,done)
|
21 | {
|
22 | return function (err)
|
23 | {
|
24 | if (err.code==code)
|
25 | done();
|
26 | else
|
27 | done(err);
|
28 | };
|
29 | };
|
30 |
|
31 | describe('transactions',function ()
|
32 | {
|
33 | var db;
|
34 |
|
35 | before(function (done)
|
36 | {
|
37 | dyngo({ dynamo: { endpoint: new AWS.Endpoint('http://localhost:8000') }, hints: false },
|
38 | function (err,_db)
|
39 | {
|
40 | db= _db;
|
41 |
|
42 | db.ensureTransactionTable().success(done).error(done);
|
43 | });
|
44 | });
|
45 |
|
46 | beforeEach(function (done)
|
47 | {
|
48 | db.test.remove().success(done)
|
49 | .error(done);
|
50 | });
|
51 |
|
52 | describe('insert',function ()
|
53 | {
|
54 |
|
55 | it('If transaction A inserts a new object transaction B cannot see it while not committed (transient)',
|
56 | function (done)
|
57 | {
|
58 | db.transaction().transaction(function (A)
|
59 | {
|
60 | db.transaction().transaction(function (B)
|
61 | {
|
62 | A.test.save({ _id: 'transient' }).success(function ()
|
63 | {
|
64 | B.test.findOne({ _id: 'transient' })
|
65 | .result(should.not.exist)
|
66 | .error(accept('notfound',done));
|
67 | }).error(done);
|
68 | }).error(done);
|
69 | }).error(done);
|
70 | });
|
71 |
|
72 | it('If transaction A inserts a new object, while there was a transient object already present, transaction B cannot see it while not committed (transient)',
|
73 | function (done)
|
74 | {
|
75 | db.transaction().transaction(function (A)
|
76 | {
|
77 | db.transaction().transaction(function (B)
|
78 | {
|
79 | A.test.save({ _id: 'transient2' }).success(function ()
|
80 | {
|
81 | A.test.save({ _id: 'transient2' }).success(function ()
|
82 | {
|
83 | B.test.findOne({ _id: 'transient2' })
|
84 | .result(should.not.exist)
|
85 | .error(accept('notfound',done));
|
86 | }).error(done);
|
87 | }).error(done);
|
88 | }).error(done);
|
89 | }).error(done);
|
90 | });
|
91 |
|
92 | it('If transaction A inserts a new object, and commit, transaction B can see the new object',
|
93 | function (done)
|
94 | {
|
95 | db.transaction().transaction(function (A)
|
96 | {
|
97 | db.transaction().transaction(function (B)
|
98 | {
|
99 | A.test.save({ _id: 'transient3' }).success(function ()
|
100 | {
|
101 | A.commit().committed(function ()
|
102 | {
|
103 | B.test.findOne({ _id: 'transient3' })
|
104 | .result(function (r)
|
105 | {
|
106 | should.exist(r);
|
107 | r._id.should.equal('transient3');
|
108 | done();
|
109 | })
|
110 | .error(done);
|
111 | }).error(done);
|
112 | }).error(done);
|
113 | }).error(done);
|
114 | }).error(done);
|
115 | });
|
116 |
|
117 | });
|
118 |
|
119 | describe('delete',function ()
|
120 | {
|
121 |
|
122 | it('If transaction A deletes an object and does not commit, transaction B can see the object, but transaction A can\'t',
|
123 | function (done)
|
124 | {
|
125 | db.test.save({ _id: 'delete1' }).success(function ()
|
126 | {
|
127 | db.transaction().transaction(function (A)
|
128 | {
|
129 | db.transaction().transaction(function (B)
|
130 | {
|
131 | A.test.remove({ _id: 'delete1' }).success(function ()
|
132 | {
|
133 | B.test.findOne({ _id: 'delete1' })
|
134 | .result(function (r)
|
135 | {
|
136 | should.exist(r);
|
137 | r._id.should.equal('delete1');
|
138 |
|
139 | A.test.findOne({ _id: 'delete1' })
|
140 | .result(should.not.exist)
|
141 | .error(accept('notfound',done));
|
142 | })
|
143 | .error(done);
|
144 |
|
145 | }).error(done);
|
146 | }).error(done);
|
147 | }).error(done);
|
148 | }).error(done);
|
149 | });
|
150 |
|
151 | it('If transaction A deletes an object and commit, transaction B cannot see the object',
|
152 | function (done)
|
153 | {
|
154 | db.test.save({ _id: 'delete2' }).success(function ()
|
155 | {
|
156 | db.transaction().transaction(function (A)
|
157 | {
|
158 | db.transaction().transaction(function (B)
|
159 | {
|
160 | A.test.remove({ _id: 'delete2' }).success(function ()
|
161 | {
|
162 | A.commit().committed(function ()
|
163 | {
|
164 | B.test.findOne({ _id: 'delete2' })
|
165 | .result(should.not.exist)
|
166 | .error(accept('notfound',done));
|
167 | }).error(done);
|
168 | }).error(done);
|
169 | }).error(done);
|
170 | }).error(done);
|
171 | }).error(done);
|
172 | });
|
173 |
|
174 | });
|
175 |
|
176 | describe('update',function ()
|
177 | {
|
178 |
|
179 | it('If transaction A updates an object and does not commit, transaction B still see the old version of the object, when A commits, B sees the new version',
|
180 | function (done)
|
181 | {
|
182 | var obj= { _id: 'update1', n: 0 };
|
183 |
|
184 | db.test.save(obj).success(function ()
|
185 | {
|
186 | db.transaction().transaction(function (A)
|
187 | {
|
188 | db.transaction().transaction(function (B)
|
189 | {
|
190 | obj.name= 'Update2';
|
191 | obj.n++;
|
192 |
|
193 | A.test.save(obj).success(function ()
|
194 | {
|
195 | B.test.findOne({ _id: 'update1' })
|
196 | .result(function (copy)
|
197 | {
|
198 | should.not.exist(copy.name);
|
199 | copy.n.should.equal(0);
|
200 |
|
201 | A.test.findOne({ _id: 'update1' })
|
202 | .result(function (copy)
|
203 | {
|
204 | copy.name.should.equal('Update2');
|
205 | copy.n.should.equal(1);
|
206 |
|
207 | A.commit().committed(function ()
|
208 | {
|
209 | B.test.findOne({ _id: 'update1' })
|
210 | .result(function (copy)
|
211 | {
|
212 | copy.name.should.equal('Update2');
|
213 | copy.n.should.equal(1);
|
214 |
|
215 | done();
|
216 | })
|
217 | .error(done);
|
218 | })
|
219 | .error(done);
|
220 | })
|
221 | .error(done);
|
222 | })
|
223 | .error(done);
|
224 | }).error(done);
|
225 | }).error(done);
|
226 | }).error(done);
|
227 | }).error(done);
|
228 | });
|
229 |
|
230 | });
|
231 |
|
232 | describe('query',function ()
|
233 | {
|
234 | it('If transaction A inserts an item without committing, transaction B should not see the inserted item until commited',
|
235 | function (done)
|
236 | {
|
237 | var items= _.collect(_.range(10),function (n) { return { _id: 'item'+n, n: n+1 } });
|
238 |
|
239 | db.test.save(items).success(function ()
|
240 | {
|
241 | db.transaction().transaction(function (A)
|
242 | {
|
243 | db.transaction().transaction(function (B)
|
244 | {
|
245 | A.test.save({ _id: 'itemS', n: 0 }).success(function ()
|
246 | {
|
247 | B.test.find().sort({ n: 1 }).limit(3)
|
248 | .results(function (objs)
|
249 | {
|
250 | objs[0].n.should.equal(1);
|
251 | objs.length.should.equal(3);
|
252 | })
|
253 | .error(done)
|
254 | .end(function ()
|
255 | {
|
256 | A.test.find().sort({ n: 1 }).limit(3)
|
257 | .results(function (objs)
|
258 | {
|
259 | objs[0].n.should.equal(0);
|
260 | objs.length.should.equal(3);
|
261 | })
|
262 | .error(done)
|
263 | .end(function ()
|
264 | {
|
265 | A.commit().committed(function ()
|
266 | {
|
267 | B.test.find().sort({ n: 1 }).limit(3)
|
268 | .results(function (objs)
|
269 | {
|
270 | objs[0].n.should.equal(0);
|
271 | objs.length.should.equal(3);
|
272 | })
|
273 | .error(done)
|
274 | .end(done);
|
275 | });
|
276 | });
|
277 | });
|
278 | }).error(done);
|
279 | });
|
280 | });
|
281 | });
|
282 | });
|
283 | });
|
284 |
|
285 | describe('concurrency',function ()
|
286 | {
|
287 | it('rollsback competing transaction',
|
288 | function (done)
|
289 | {
|
290 | db.test.save({ _id: 'hot', name: 'Hot' }).success(function ()
|
291 | {
|
292 | db.transaction().transaction(function (A)
|
293 | {
|
294 | db.transaction().transaction(function (B)
|
295 | {
|
296 | A.test.findOne({ _id: 'hot' })
|
297 | .result(function (hotA)
|
298 | {
|
299 | hotA.name= 'HotA';
|
300 |
|
301 | B.test.findOne({ _id: 'hot' })
|
302 | .result(function (hotB)
|
303 | {
|
304 | hotB.name= 'HotB';
|
305 |
|
306 | A.test.save(hotA).success(function ()
|
307 | {
|
308 | B.test.save(hotB).success(function ()
|
309 | {
|
310 | var committedA= chai.spy();
|
311 |
|
312 | A.commit()
|
313 | .committed(committedA)
|
314 | .error(function (err)
|
315 | {
|
316 | if (err.code=='rolledback')
|
317 | B.commit().committed(function ()
|
318 | {
|
319 | db.test.findOne({ _id: 'hot' })
|
320 | .result(function (obj)
|
321 | {
|
322 | committedA.should.not.have.been.called();
|
323 | obj.name.should.equal('HotB');
|
324 | done();
|
325 | })
|
326 | .error(done);
|
327 | })
|
328 | .error(done);
|
329 | else
|
330 | done(err);
|
331 | });
|
332 | })
|
333 | .error(done);
|
334 | })
|
335 | .error(done);
|
336 | })
|
337 | .error(done);
|
338 | })
|
339 | .error(done);
|
340 |
|
341 | }).error(done);
|
342 | }).error(done);
|
343 | }).error(done);
|
344 | });
|
345 | });
|
346 | });
|