@@ -2,6 +2,7 @@ const ParseServerRESTController = require('../lib/ParseServerRESTController')
2
2
. ParseServerRESTController ;
3
3
const ParseServer = require ( '../lib/ParseServer' ) . default ;
4
4
const Parse = require ( 'parse/node' ) . Parse ;
5
+ const TestUtils = require ( '../lib/TestUtils' ) ;
5
6
6
7
let RESTController ;
7
8
@@ -40,7 +41,7 @@ describe('ParseServerRESTController', () => {
40
41
) ;
41
42
} ) ;
42
43
43
- it ( 'should handle a POST batch' , done => {
44
+ it ( 'should handle a POST batch without transaction ' , done => {
44
45
RESTController . request ( 'POST' , 'batch' , {
45
46
requests : [
46
47
{
@@ -69,6 +70,272 @@ describe('ParseServerRESTController', () => {
69
70
) ;
70
71
} ) ;
71
72
73
+ it ( 'should handle a POST batch with transaction=false' , done => {
74
+ RESTController . request ( 'POST' , 'batch' , {
75
+ requests : [
76
+ {
77
+ method : 'GET' ,
78
+ path : '/classes/MyObject' ,
79
+ } ,
80
+ {
81
+ method : 'POST' ,
82
+ path : '/classes/MyObject' ,
83
+ body : { key : 'value' } ,
84
+ } ,
85
+ {
86
+ method : 'GET' ,
87
+ path : '/classes/MyObject' ,
88
+ } ,
89
+ ] ,
90
+ transaction : false ,
91
+ } ) . then (
92
+ res => {
93
+ expect ( res . length ) . toBe ( 3 ) ;
94
+ done ( ) ;
95
+ } ,
96
+ err => {
97
+ jfail ( err ) ;
98
+ done ( ) ;
99
+ }
100
+ ) ;
101
+ } ) ;
102
+
103
+ if (
104
+ ( process . env . MONGODB_VERSION === '4.0.4' &&
105
+ process . env . MONGODB_TOPOLOGY === 'replicaset' &&
106
+ process . env . MONGODB_STORAGE_ENGINE === 'wiredTiger' ) ||
107
+ process . env . PARSE_SERVER_TEST_DB === 'postgres'
108
+ ) {
109
+ describe ( 'transactions' , ( ) => {
110
+ beforeAll ( async ( ) => {
111
+ if (
112
+ process . env . MONGODB_VERSION === '4.0.4' &&
113
+ process . env . MONGODB_TOPOLOGY === 'replicaset' &&
114
+ process . env . MONGODB_STORAGE_ENGINE === 'wiredTiger'
115
+ ) {
116
+ await reconfigureServer ( {
117
+ databaseAdapter : undefined ,
118
+ databaseURI :
119
+ 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase?replicaSet=replicaset' ,
120
+ } ) ;
121
+ }
122
+ } ) ;
123
+
124
+ beforeEach ( async ( ) => {
125
+ await TestUtils . destroyAllDataPermanently ( true ) ;
126
+ } ) ;
127
+
128
+ it ( 'should handle a batch request with transaction = true' , done => {
129
+ const myObject = new Parse . Object ( 'MyObject' ) ; // This is important because transaction only works on pre-existing collections
130
+ myObject
131
+ . save ( )
132
+ . then ( ( ) => {
133
+ return myObject . destroy ( ) ;
134
+ } )
135
+ . then ( ( ) => {
136
+ spyOn ( databaseAdapter , 'createObject' ) . and . callThrough ( ) ;
137
+
138
+ RESTController . request ( 'POST' , 'batch' , {
139
+ requests : [
140
+ {
141
+ method : 'POST' ,
142
+ path : '/1/classes/MyObject' ,
143
+ body : { key : 'value1' } ,
144
+ } ,
145
+ {
146
+ method : 'POST' ,
147
+ path : '/1/classes/MyObject' ,
148
+ body : { key : 'value2' } ,
149
+ } ,
150
+ ] ,
151
+ transaction : true ,
152
+ } ) . then ( response => {
153
+ expect ( response . length ) . toEqual ( 2 ) ;
154
+ expect ( response [ 0 ] . success . objectId ) . toBeDefined ( ) ;
155
+ expect ( response [ 0 ] . success . createdAt ) . toBeDefined ( ) ;
156
+ expect ( response [ 1 ] . success . objectId ) . toBeDefined ( ) ;
157
+ expect ( response [ 1 ] . success . createdAt ) . toBeDefined ( ) ;
158
+ const query = new Parse . Query ( 'MyObject' ) ;
159
+ query . find ( ) . then ( results => {
160
+ expect ( databaseAdapter . createObject . calls . count ( ) ) . toBe ( 2 ) ;
161
+ expect ( databaseAdapter . createObject . calls . argsFor ( 0 ) [ 3 ] ) . toBe (
162
+ databaseAdapter . createObject . calls . argsFor ( 1 ) [ 3 ]
163
+ ) ;
164
+ expect ( results . map ( result => result . get ( 'key' ) ) . sort ( ) ) . toEqual (
165
+ [ 'value1' , 'value2' ]
166
+ ) ;
167
+ done ( ) ;
168
+ } ) ;
169
+ } ) ;
170
+ } ) ;
171
+ } ) ;
172
+
173
+ it ( 'should not save anything when one operation fails in a transaction' , done => {
174
+ const myObject = new Parse . Object ( 'MyObject' ) ; // This is important because transaction only works on pre-existing collections
175
+ myObject
176
+ . save ( )
177
+ . then ( ( ) => {
178
+ return myObject . destroy ( ) ;
179
+ } )
180
+ . then ( ( ) => {
181
+ RESTController . request ( 'POST' , 'batch' , {
182
+ requests : [
183
+ {
184
+ method : 'POST' ,
185
+ path : '/1/classes/MyObject' ,
186
+ body : { key : 'value1' } ,
187
+ } ,
188
+ {
189
+ method : 'POST' ,
190
+ path : '/1/classes/MyObject' ,
191
+ body : { key : 10 } ,
192
+ } ,
193
+ ] ,
194
+ transaction : true ,
195
+ } ) . catch ( error => {
196
+ expect ( error . message ) . toBeDefined ( ) ;
197
+ const query = new Parse . Query ( 'MyObject' ) ;
198
+ query . find ( ) . then ( results => {
199
+ expect ( results . length ) . toBe ( 0 ) ;
200
+ done ( ) ;
201
+ } ) ;
202
+ } ) ;
203
+ } ) ;
204
+ } ) ;
205
+
206
+ it ( 'should generate separate session for each call' , async ( ) => {
207
+ const myObject = new Parse . Object ( 'MyObject' ) ; // This is important because transaction only works on pre-existing collections
208
+ await myObject . save ( ) ;
209
+ await myObject . destroy ( ) ;
210
+
211
+ const myObject2 = new Parse . Object ( 'MyObject2' ) ; // This is important because transaction only works on pre-existing collections
212
+ await myObject2 . save ( ) ;
213
+ await myObject2 . destroy ( ) ;
214
+
215
+ spyOn ( databaseAdapter , 'createObject' ) . and . callThrough ( ) ;
216
+
217
+ let myObjectCalls = 0 ;
218
+ Parse . Cloud . beforeSave ( 'MyObject' , async ( ) => {
219
+ myObjectCalls ++ ;
220
+ if ( myObjectCalls === 2 ) {
221
+ try {
222
+ await RESTController . request ( 'POST' , 'batch' , {
223
+ requests : [
224
+ {
225
+ method : 'POST' ,
226
+ path : '/1/classes/MyObject2' ,
227
+ body : { key : 'value1' } ,
228
+ } ,
229
+ {
230
+ method : 'POST' ,
231
+ path : '/1/classes/MyObject2' ,
232
+ body : { key : 10 } ,
233
+ } ,
234
+ ] ,
235
+ transaction : true ,
236
+ } ) ;
237
+ fail ( 'should fail' ) ;
238
+ } catch ( e ) {
239
+ expect ( e ) . toBeDefined ( ) ;
240
+ }
241
+ }
242
+ } ) ;
243
+
244
+ const response = await RESTController . request ( 'POST' , 'batch' , {
245
+ requests : [
246
+ {
247
+ method : 'POST' ,
248
+ path : '/1/classes/MyObject' ,
249
+ body : { key : 'value1' } ,
250
+ } ,
251
+ {
252
+ method : 'POST' ,
253
+ path : '/1/classes/MyObject' ,
254
+ body : { key : 'value2' } ,
255
+ } ,
256
+ ] ,
257
+ transaction : true ,
258
+ } ) ;
259
+
260
+ expect ( response . length ) . toEqual ( 2 ) ;
261
+ expect ( response [ 0 ] . success . objectId ) . toBeDefined ( ) ;
262
+ expect ( response [ 0 ] . success . createdAt ) . toBeDefined ( ) ;
263
+ expect ( response [ 1 ] . success . objectId ) . toBeDefined ( ) ;
264
+ expect ( response [ 1 ] . success . createdAt ) . toBeDefined ( ) ;
265
+
266
+ await RESTController . request ( 'POST' , 'batch' , {
267
+ requests : [
268
+ {
269
+ method : 'POST' ,
270
+ path : '/1/classes/MyObject3' ,
271
+ body : { key : 'value1' } ,
272
+ } ,
273
+ {
274
+ method : 'POST' ,
275
+ path : '/1/classes/MyObject3' ,
276
+ body : { key : 'value2' } ,
277
+ } ,
278
+ ] ,
279
+ } ) ;
280
+
281
+ const query = new Parse . Query ( 'MyObject' ) ;
282
+ const results = await query . find ( ) ;
283
+ expect ( results . map ( result => result . get ( 'key' ) ) . sort ( ) ) . toEqual ( [
284
+ 'value1' ,
285
+ 'value2' ,
286
+ ] ) ;
287
+
288
+ const query2 = new Parse . Query ( 'MyObject2' ) ;
289
+ const results2 = await query2 . find ( ) ;
290
+ expect ( results2 . length ) . toEqual ( 0 ) ;
291
+
292
+ const query3 = new Parse . Query ( 'MyObject3' ) ;
293
+ const results3 = await query3 . find ( ) ;
294
+ expect ( results3 . map ( result => result . get ( 'key' ) ) . sort ( ) ) . toEqual ( [
295
+ 'value1' ,
296
+ 'value2' ,
297
+ ] ) ;
298
+
299
+ expect ( databaseAdapter . createObject . calls . count ( ) ) . toBe ( 5 ) ;
300
+ let transactionalSession ;
301
+ let transactionalSession2 ;
302
+ let myObjectDBCalls = 0 ;
303
+ let myObject2DBCalls = 0 ;
304
+ let myObject3DBCalls = 0 ;
305
+ for ( let i = 0 ; i < 5 ; i ++ ) {
306
+ const args = databaseAdapter . createObject . calls . argsFor ( i ) ;
307
+ switch ( args [ 0 ] ) {
308
+ case 'MyObject' :
309
+ myObjectDBCalls ++ ;
310
+ if ( ! transactionalSession ) {
311
+ transactionalSession = args [ 3 ] ;
312
+ } else {
313
+ expect ( transactionalSession ) . toBe ( args [ 3 ] ) ;
314
+ }
315
+ if ( transactionalSession2 ) {
316
+ expect ( transactionalSession2 ) . not . toBe ( args [ 3 ] ) ;
317
+ }
318
+ break ;
319
+ case 'MyObject2' :
320
+ myObject2DBCalls ++ ;
321
+ transactionalSession2 = args [ 3 ] ;
322
+ if ( transactionalSession ) {
323
+ expect ( transactionalSession ) . not . toBe ( args [ 3 ] ) ;
324
+ }
325
+ break ;
326
+ case 'MyObject3' :
327
+ myObject3DBCalls ++ ;
328
+ expect ( args [ 3 ] ) . toEqual ( null ) ;
329
+ break ;
330
+ }
331
+ }
332
+ expect ( myObjectDBCalls ) . toEqual ( 2 ) ;
333
+ expect ( myObject2DBCalls ) . toEqual ( 1 ) ;
334
+ expect ( myObject3DBCalls ) . toEqual ( 2 ) ;
335
+ } ) ;
336
+ } ) ;
337
+ }
338
+
72
339
it ( 'should handle a POST request' , done => {
73
340
RESTController . request ( 'POST' , '/classes/MyObject' , { key : 'value' } )
74
341
. then ( ( ) => {
0 commit comments