Skip to content

Commit 069275d

Browse files
authored
Fix for #1840, Strip operations from results, forwards delete operations to SDKs (#1946)
* Adding a test demonstrating issue #1840. * Fixes #1840 * Adds failing test with other use case - That test fails on parse.com as well * Bumps parse to 1.9.0 * exclude pg db * Exclude pg on other test * Adds clientSDK compatibility check for forward deletion - Mark js1.9.0 as compatible * Strips all operations from result - fix for #1606
1 parent fcfe2a0 commit 069275d

7 files changed

+216
-39
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"redis": "2.6.2",
4343
"request": "2.73.0",
4444
"request-promise": "3.0.0",
45+
"semver": "^5.2.0",
4546
"tv4": "1.2.7",
4647
"winston": "2.2.0",
4748
"winston-daily-rotate-file": "1.1.5",

spec/ClientSDK.spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var ClientSDK = require('../src/ClientSDK');
2+
3+
describe('ClientSDK', () => {
4+
it('should properly parse the SDK versions', () => {
5+
let clientSDKFromVersion = ClientSDK.fromString;
6+
expect(clientSDKFromVersion('i1.1.1')).toEqual({
7+
sdk: 'i',
8+
version: '1.1.1'
9+
});
10+
expect(clientSDKFromVersion('i1')).toEqual({
11+
sdk: 'i',
12+
version: '1'
13+
});
14+
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
15+
sdk: 'apple-tv',
16+
version: '1.13.0'
17+
});
18+
expect(clientSDKFromVersion('js1.9.0')).toEqual({
19+
sdk: 'js',
20+
version: '1.9.0'
21+
});
22+
});
23+
24+
it('should properly sastisfy', () => {
25+
expect(ClientSDK.compatible({
26+
js: '>=1.9.0'
27+
})("js1.9.0")).toBe(true);
28+
29+
expect(ClientSDK.compatible({
30+
js: '>=1.9.0'
31+
})("js2.0.0")).toBe(true);
32+
33+
expect(ClientSDK.compatible({
34+
js: '>=1.9.0'
35+
})("js1.8.0")).toBe(false);
36+
37+
expect(ClientSDK.compatible({
38+
js: '>=1.9.0'
39+
})(undefined)).toBe(true);
40+
})
41+
})

spec/CloudCode.spec.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,4 +666,114 @@ describe('Cloud Code', () => {
666666
done();
667667
});
668668
});
669+
670+
it_exclude_dbs(['postgres'])('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
671+
var TestObject = Parse.Object.extend('TestObject');
672+
var NoBeforeSaveObject = Parse.Object.extend('NoBeforeSave');
673+
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
674+
675+
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
676+
var object = req.object;
677+
object.set('before', 'save');
678+
res.success();
679+
});
680+
681+
Parse.Cloud.define('removeme', (req, res) => {
682+
var testObject = new TestObject();
683+
testObject.save()
684+
.then(testObject => {
685+
var object = new NoBeforeSaveObject({remove: testObject});
686+
return object.save();
687+
})
688+
.then(object => {
689+
object.unset('remove');
690+
return object.save();
691+
})
692+
.then(object => {
693+
res.success(object);
694+
});
695+
});
696+
697+
Parse.Cloud.define('removeme2', (req, res) => {
698+
var testObject = new TestObject();
699+
testObject.save()
700+
.then(testObject => {
701+
var object = new BeforeSaveObject({remove: testObject});
702+
return object.save();
703+
})
704+
.then(object => {
705+
object.unset('remove');
706+
return object.save();
707+
})
708+
.then(object => {
709+
res.success(object);
710+
});
711+
});
712+
713+
Parse.Cloud.run('removeme')
714+
.then(aNoBeforeSaveObj => {
715+
expect(aNoBeforeSaveObj.get('remove')).toEqual(undefined);
716+
717+
return Parse.Cloud.run('removeme2');
718+
})
719+
.then(aBeforeSaveObj => {
720+
expect(aBeforeSaveObj.get('before')).toEqual('save');
721+
expect(aBeforeSaveObj.get('remove')).toEqual(undefined);
722+
done();
723+
});
724+
});
725+
726+
it_exclude_dbs(['postgres'])('should fully delete objects when using `unset` with beforeSave (regression test for #1840)', done => {
727+
var TestObject = Parse.Object.extend('TestObject');
728+
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
729+
730+
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
731+
var object = req.object;
732+
object.set('before', 'save');
733+
object.unset('remove');
734+
res.success();
735+
});
736+
737+
let object;
738+
let testObject = new TestObject({key: 'value'});
739+
testObject.save().then(() => {
740+
object = new BeforeSaveObject();
741+
return object.save().then(() => {
742+
object.set({remove:testObject})
743+
return object.save();
744+
});
745+
}).then((objectAgain) => {
746+
expect(objectAgain.get('remove')).toBeUndefined();
747+
expect(object.get('remove')).toBeUndefined();
748+
done();
749+
}).fail((err) => {
750+
console.error(err);
751+
done();
752+
})
753+
});
754+
755+
it_exclude_dbs(['postgres'])('should not include relation op (regression test for #1606)', done => {
756+
var TestObject = Parse.Object.extend('TestObject');
757+
var BeforeSaveObject = Parse.Object.extend('BeforeSaveChanged');
758+
let testObj;
759+
Parse.Cloud.beforeSave('BeforeSaveChanged', (req, res) => {
760+
var object = req.object;
761+
object.set('before', 'save');
762+
testObj = new TestObject();
763+
testObj.save().then(() => {
764+
object.relation('testsRelation').add(testObj);
765+
res.success();
766+
})
767+
});
768+
769+
let object = new BeforeSaveObject();
770+
object.save().then((objectAgain) => {
771+
// Originally it would throw as it would be a non-relation
772+
expect(() => { objectAgain.relation('testsRelation') }).not.toThrow();
773+
done();
774+
}).fail((err) => {
775+
console.error(err);
776+
done();
777+
})
778+
});
669779
});

spec/Middlewares.spec.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,4 @@ describe('middlewares', () => {
6666
});
6767
});
6868
});
69-
70-
it('should properly parse the SDK versions', () => {
71-
let clientSDKFromVersion = middlewares.clientSDKFromVersion;
72-
expect(clientSDKFromVersion('i1.1.1')).toEqual({
73-
sdk: 'i',
74-
version: '1.1.1'
75-
});
76-
expect(clientSDKFromVersion('i1')).toEqual({
77-
sdk: 'i',
78-
version: '1'
79-
});
80-
expect(clientSDKFromVersion('apple-tv1.13.0')).toEqual({
81-
sdk: 'apple-tv',
82-
version: '1.13.0'
83-
});
84-
expect(clientSDKFromVersion('js1.9.0')).toEqual({
85-
sdk: 'js',
86-
version: '1.9.0'
87-
});
88-
})
8969
});

src/ClientSDK.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
var semver = require('semver');
2+
3+
function compatible(compatibleSDK) {
4+
return function(clientSDK) {
5+
if (typeof clientSDK === 'string') {
6+
clientSDK = fromString(clientSDK);
7+
}
8+
// REST API, or custom SDK
9+
if (!clientSDK) {
10+
return true;
11+
}
12+
let clientVersion = clientSDK.version;
13+
let compatiblityVersion = compatibleSDK[clientSDK.sdk];
14+
return semver.satisfies(clientVersion, compatiblityVersion);
15+
}
16+
}
17+
18+
function supportsForwardDelete(clientSDK) {
19+
return compatible({
20+
js: '>=1.9.0'
21+
})(clientSDK);
22+
}
23+
24+
function fromString(version) {
25+
let versionRE = /([-a-zA-Z]+)([0-9\.]+)/;
26+
let match = version.toLowerCase().match(versionRE);
27+
if (match && match.length === 3) {
28+
return {
29+
sdk: match[1],
30+
version: match[2]
31+
}
32+
}
33+
return undefined;
34+
}
35+
36+
module.exports = {
37+
compatible,
38+
supportsForwardDelete,
39+
fromString
40+
}

src/RestWrite.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var cryptoUtils = require('./cryptoUtils');
1111
var passwordCrypto = require('./password');
1212
var Parse = require('parse/node');
1313
var triggers = require('./triggers');
14+
var ClientSDK = require('./ClientSDK');
1415
import RestQuery from './RestQuery';
1516
import _ from 'lodash';
1617

@@ -774,9 +775,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
774775
.then(response => {
775776
response.updatedAt = this.updatedAt;
776777
if (this.storage.changedByTrigger) {
777-
Object.keys(this.data).forEach(fieldName => {
778-
response[fieldName] = response[fieldName] || this.data[fieldName];
779-
});
778+
this.updateResponseWithData(response, this.data);
780779
}
781780
this.response = { response };
782781
});
@@ -834,9 +833,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
834833
response.username = this.data.username;
835834
}
836835
if (this.storage.changedByTrigger) {
837-
Object.keys(this.data).forEach(fieldName => {
838-
response[fieldName] = response[fieldName] || this.data[fieldName];
839-
});
836+
this.updateResponseWithData(response, this.data);
840837
}
841838
this.response = {
842839
status: 201,
@@ -925,5 +922,24 @@ RestWrite.prototype.cleanUserAuthData = function() {
925922
}
926923
};
927924

925+
RestWrite.prototype.updateResponseWithData = function(response, data) {
926+
let clientSupportsDelete = ClientSDK.supportsForwardDelete(this.clientSDK);
927+
Object.keys(data).forEach(fieldName => {
928+
let dataValue = data[fieldName];
929+
let responseValue = response[fieldName];
930+
931+
response[fieldName] = responseValue || dataValue;
932+
933+
// Strips operations from responses
934+
if (response[fieldName] && response[fieldName].__op) {
935+
delete response[fieldName];
936+
if (clientSupportsDelete && dataValue.__op == 'Delete') {
937+
response[fieldName] = dataValue;
938+
}
939+
}
940+
});
941+
return response;
942+
}
943+
928944
export default RestWrite;
929945
module.exports = RestWrite;

src/middlewares.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,7 @@ var Parse = require('parse/node').Parse;
55

66
var auth = require('./Auth');
77
var Config = require('./Config');
8-
9-
function clientSDKFromVersion(version) {
10-
let versionRE = /([-a-zA-Z]+)([0-9\.]+)/;
11-
let match = version.toLowerCase().match(versionRE);
12-
if (match && match.length === 3) {
13-
return {
14-
sdk: match[1],
15-
version: match[2]
16-
}
17-
}
18-
}
8+
var ClientSDK = require('./ClientSDK');
199

2010
// Checks that the request is authorized for this app and checks user
2111
// auth too.
@@ -106,7 +96,7 @@ function handleParseHeaders(req, res, next) {
10696
}
10797

10898
if (info.clientVersion) {
109-
info.clientSDK = clientSDKFromVersion(info.clientVersion);
99+
info.clientSDK = ClientSDK.fromString(info.clientVersion);
110100
}
111101

112102
if (fileViaJSON) {
@@ -300,5 +290,4 @@ module.exports = {
300290
handleParseHeaders: handleParseHeaders,
301291
enforceMasterKeyAccess: enforceMasterKeyAccess,
302292
promiseEnforceMasterKeyAccess,
303-
clientSDKFromVersion
304293
};

0 commit comments

Comments
 (0)