Required fields and default values (#5835)
* Add field options to mongo schema metadata * Add/fix test with fields options * Add required validation failing test * Add more tests * Only set default value if field is undefined * Fix redis test * Fix tests * Test for creating a new class with field options * Validate default value type * fix lint (weird) * Fix lint another way * Add tests for beforeSave trigger and solve small issue regarding the use of unset in the beforeSave trigger
This commit is contained in:
committed by
GitHub
parent
d3810c2eba
commit
fd637ff4f8
@@ -188,7 +188,7 @@ describe_only(() => {
|
|||||||
const object = new TestObject();
|
const object = new TestObject();
|
||||||
object.set('foo', 'bar');
|
object.set('foo', 'bar');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(2);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(2);
|
expect(putSpy.calls.count()).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ describe_only(() => {
|
|||||||
booleanField: true,
|
booleanField: true,
|
||||||
});
|
});
|
||||||
await container.save();
|
await container.save();
|
||||||
expect(getSpy.calls.count()).toBe(2);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(2);
|
expect(putSpy.calls.count()).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ describe_only(() => {
|
|||||||
|
|
||||||
object.set('foo', 'barz');
|
object.set('foo', 'barz');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(2);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(0);
|
expect(putSpy.calls.count()).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ describe_only(() => {
|
|||||||
objects.push(object);
|
objects.push(object);
|
||||||
}
|
}
|
||||||
await Parse.Object.saveAll(objects);
|
await Parse.Object.saveAll(objects);
|
||||||
expect(getSpy.calls.count()).toBe(11);
|
expect(getSpy.calls.count()).toBe(21);
|
||||||
expect(putSpy.calls.count()).toBe(10);
|
expect(putSpy.calls.count()).toBe(10);
|
||||||
|
|
||||||
getSpy.calls.reset();
|
getSpy.calls.reset();
|
||||||
@@ -258,7 +258,7 @@ describe_only(() => {
|
|||||||
objects.push(object);
|
objects.push(object);
|
||||||
}
|
}
|
||||||
await Parse.Object.saveAll(objects, { batchSize: 5 });
|
await Parse.Object.saveAll(objects, { batchSize: 5 });
|
||||||
expect(getSpy.calls.count()).toBe(12);
|
expect(getSpy.calls.count()).toBe(22);
|
||||||
expect(putSpy.calls.count()).toBe(5);
|
expect(putSpy.calls.count()).toBe(5);
|
||||||
|
|
||||||
getSpy.calls.reset();
|
getSpy.calls.reset();
|
||||||
@@ -279,7 +279,7 @@ describe_only(() => {
|
|||||||
|
|
||||||
object.set('new', 'barz');
|
object.set('new', 'barz');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(2);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ describe_only(() => {
|
|||||||
booleanField: true,
|
booleanField: true,
|
||||||
});
|
});
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(2);
|
expect(getSpy.calls.count()).toBe(3);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -309,7 +309,7 @@ describe_only(() => {
|
|||||||
user.setPassword('testing');
|
user.setPassword('testing');
|
||||||
await user.signUp();
|
await user.signUp();
|
||||||
|
|
||||||
expect(getSpy.calls.count()).toBe(6);
|
expect(getSpy.calls.count()).toBe(8);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ describe_only(() => {
|
|||||||
|
|
||||||
object.set('foo', 'bar');
|
object.set('foo', 'bar');
|
||||||
await object.save();
|
await object.save();
|
||||||
expect(getSpy.calls.count()).toBe(3);
|
expect(getSpy.calls.count()).toBe(4);
|
||||||
expect(putSpy.calls.count()).toBe(1);
|
expect(putSpy.calls.count()).toBe(1);
|
||||||
|
|
||||||
getSpy.calls.reset();
|
getSpy.calls.reset();
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ describe('SchemaController', () => {
|
|||||||
schema
|
schema
|
||||||
.addClassIfNotExists('_InvalidName', { foo: { type: 'String' } })
|
.addClassIfNotExists('_InvalidName', { foo: { type: 'String' } })
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.error).toEqual(
|
expect(error.message).toEqual(
|
||||||
'Invalid classname: _InvalidName, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
'Invalid classname: _InvalidName, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
@@ -522,7 +522,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_KEY_NAME);
|
expect(error.code).toEqual(Parse.Error.INVALID_KEY_NAME);
|
||||||
expect(error.error).toEqual('invalid field name: 0InvalidName');
|
expect(error.message).toEqual('invalid field name: 0InvalidName');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -535,7 +535,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(136);
|
expect(error.code).toEqual(136);
|
||||||
expect(error.error).toEqual('field objectId cannot be added');
|
expect(error.message).toEqual('field objectId cannot be added');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -550,7 +550,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(136);
|
expect(error.code).toEqual(136);
|
||||||
expect(error.error).toEqual('field localeIdentifier cannot be added');
|
expect(error.message).toEqual('field localeIdentifier cannot be added');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -565,7 +565,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||||
expect(error.error).toEqual('invalid JSON');
|
expect(error.message).toEqual('invalid JSON');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -580,7 +580,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(135);
|
expect(error.code).toEqual(135);
|
||||||
expect(error.error).toEqual('type Pointer needs a class name');
|
expect(error.message).toEqual('type Pointer needs a class name');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -595,7 +595,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||||
expect(error.error).toEqual('invalid JSON');
|
expect(error.message).toEqual('invalid JSON');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -610,7 +610,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(135);
|
expect(error.code).toEqual(135);
|
||||||
expect(error.error).toEqual('type Relation needs a class name');
|
expect(error.message).toEqual('type Relation needs a class name');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -625,7 +625,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
|
||||||
expect(error.error).toEqual('invalid JSON');
|
expect(error.message).toEqual('invalid JSON');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -640,7 +640,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||||
expect(error.error).toEqual(
|
expect(error.message).toEqual(
|
||||||
'Invalid classname: not a valid class name, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
'Invalid classname: not a valid class name, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
@@ -657,7 +657,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||||
expect(error.error).toEqual(
|
expect(error.message).toEqual(
|
||||||
'Invalid classname: not a valid class name, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
'Invalid classname: not a valid class name, classnames can only have alphanumeric characters and _, and must start with an alpha character '
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
@@ -674,7 +674,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||||
expect(error.error).toEqual('invalid field type: Unknown');
|
expect(error.message).toEqual('invalid field type: Unknown');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -929,7 +929,7 @@ describe('SchemaController', () => {
|
|||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||||
expect(error.error).toEqual(
|
expect(error.message).toEqual(
|
||||||
'currently, only one GeoPoint field may exist in an object. Adding geo2 when geo1 already exists.'
|
'currently, only one GeoPoint field may exist in an object. Adding geo2 when geo1 already exists.'
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -406,6 +406,124 @@ describe('schemas', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('responds with all fields and options when you create a class with field options', done => {
|
||||||
|
request({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'NewClassWithOptions',
|
||||||
|
fields: {
|
||||||
|
foo1: { type: 'Number' },
|
||||||
|
foo2: { type: 'Number', required: true, defaultValue: 10 },
|
||||||
|
foo3: {
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
defaultValue: 'some string',
|
||||||
|
},
|
||||||
|
foo4: { type: 'Date', required: true },
|
||||||
|
foo5: { type: 'Number', defaultValue: 5 },
|
||||||
|
ptr: { type: 'Pointer', targetClass: 'SomeClass', required: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(async response => {
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
className: 'NewClassWithOptions',
|
||||||
|
fields: {
|
||||||
|
ACL: { type: 'ACL' },
|
||||||
|
createdAt: { type: 'Date' },
|
||||||
|
updatedAt: { type: 'Date' },
|
||||||
|
objectId: { type: 'String' },
|
||||||
|
foo1: { type: 'Number' },
|
||||||
|
foo2: { type: 'Number', required: true, defaultValue: 10 },
|
||||||
|
foo3: {
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
defaultValue: 'some string',
|
||||||
|
},
|
||||||
|
foo4: { type: 'Date', required: true },
|
||||||
|
foo5: { type: 'Number', defaultValue: 5 },
|
||||||
|
ptr: { type: 'Pointer', targetClass: 'SomeClass', required: false },
|
||||||
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions,
|
||||||
|
});
|
||||||
|
const obj = new Parse.Object('NewClassWithOptions');
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
}
|
||||||
|
const date = new Date();
|
||||||
|
obj.set('foo4', date);
|
||||||
|
await obj.save();
|
||||||
|
expect(obj.get('foo1')).toBeUndefined();
|
||||||
|
expect(obj.get('foo2')).toEqual(10);
|
||||||
|
expect(obj.get('foo3')).toEqual('some string');
|
||||||
|
expect(obj.get('foo4')).toEqual(date);
|
||||||
|
expect(obj.get('foo5')).toEqual(5);
|
||||||
|
expect(obj.get('ptr')).toBeUndefined();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validated the data type of default values when creating a new class', async () => {
|
||||||
|
try {
|
||||||
|
await request({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'NewClassWithValidation',
|
||||||
|
fields: {
|
||||||
|
foo: { type: 'String', defaultValue: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fail('should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.data.error).toEqual(
|
||||||
|
'schema mismatch for NewClassWithValidation.foo default value; expected String but got Number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validated the data type of default values when adding new fields', async () => {
|
||||||
|
try {
|
||||||
|
await request({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'NewClassWithValidation',
|
||||||
|
fields: {
|
||||||
|
foo: { type: 'String', defaultValue: 'some value' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await request({
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClassWithValidation',
|
||||||
|
method: 'PUT',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'NewClassWithValidation',
|
||||||
|
fields: {
|
||||||
|
foo2: { type: 'String', defaultValue: 10 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fail('should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.data.error).toEqual(
|
||||||
|
'schema mismatch for NewClassWithValidation.foo2 default value; expected String but got Number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('responds with all fields when getting incomplete schema', done => {
|
it('responds with all fields when getting incomplete schema', done => {
|
||||||
config.database
|
config.database
|
||||||
.loadSchema()
|
.loadSchema()
|
||||||
@@ -740,6 +858,319 @@ describe('schemas', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('lets you add fields with options', done => {
|
||||||
|
request({
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {},
|
||||||
|
}).then(() => {
|
||||||
|
request({
|
||||||
|
method: 'PUT',
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
fields: {
|
||||||
|
newField: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(response => {
|
||||||
|
expect(
|
||||||
|
dd(response.data, {
|
||||||
|
className: 'NewClass',
|
||||||
|
fields: {
|
||||||
|
ACL: { type: 'ACL' },
|
||||||
|
createdAt: { type: 'Date' },
|
||||||
|
objectId: { type: 'String' },
|
||||||
|
updatedAt: { type: 'Date' },
|
||||||
|
newField: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions,
|
||||||
|
})
|
||||||
|
).toEqual(undefined);
|
||||||
|
request({
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
}).then(response => {
|
||||||
|
expect(response.data).toEqual({
|
||||||
|
className: 'NewClass',
|
||||||
|
fields: {
|
||||||
|
ACL: { type: 'ACL' },
|
||||||
|
createdAt: { type: 'Date' },
|
||||||
|
updatedAt: { type: 'Date' },
|
||||||
|
objectId: { type: 'String' },
|
||||||
|
newField: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
classLevelPermissions: defaultClassLevelPermissions,
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate required fields', done => {
|
||||||
|
request({
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {},
|
||||||
|
}).then(() => {
|
||||||
|
request({
|
||||||
|
method: 'PUT',
|
||||||
|
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
fields: {
|
||||||
|
newRequiredField: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
newRequiredFieldWithDefaultValue: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
newNotRequiredField: {
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
newNotRequiredFieldWithDefaultValue: {
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
newRegularFieldWithDefaultValue: {
|
||||||
|
type: 'String',
|
||||||
|
defaultValue: 'some value',
|
||||||
|
},
|
||||||
|
newRegularField: {
|
||||||
|
type: 'String',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).then(async () => {
|
||||||
|
let obj = new Parse.Object('NewClass');
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('Should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
expect(e.message).toEqual('newRequiredField is required');
|
||||||
|
}
|
||||||
|
obj.set('newRequiredField', 'some value');
|
||||||
|
await obj.save();
|
||||||
|
expect(obj.get('newRequiredField')).toEqual('some value');
|
||||||
|
expect(obj.get('newRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value'
|
||||||
|
);
|
||||||
|
expect(obj.get('newNotRequiredField')).toEqual(undefined);
|
||||||
|
expect(obj.get('newNotRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value'
|
||||||
|
);
|
||||||
|
expect(obj.get('newRegularField')).toEqual(undefined);
|
||||||
|
obj.set('newRequiredField', null);
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('Should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
expect(e.message).toEqual('newRequiredField is required');
|
||||||
|
}
|
||||||
|
obj.unset('newRequiredField');
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('Should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
expect(e.message).toEqual('newRequiredField is required');
|
||||||
|
}
|
||||||
|
obj.set('newRequiredField', 'some value2');
|
||||||
|
await obj.save();
|
||||||
|
expect(obj.get('newRequiredField')).toEqual('some value2');
|
||||||
|
expect(obj.get('newRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value'
|
||||||
|
);
|
||||||
|
expect(obj.get('newNotRequiredField')).toEqual(undefined);
|
||||||
|
expect(obj.get('newNotRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value'
|
||||||
|
);
|
||||||
|
expect(obj.get('newRegularField')).toEqual(undefined);
|
||||||
|
obj.unset('newRequiredFieldWithDefaultValue');
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('Should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
'newRequiredFieldWithDefaultValue is required'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
obj.set('newRequiredFieldWithDefaultValue', '');
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('Should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.code).toEqual(142);
|
||||||
|
expect(e.message).toEqual(
|
||||||
|
'newRequiredFieldWithDefaultValue is required'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
obj.set('newRequiredFieldWithDefaultValue', 'some value2');
|
||||||
|
obj.set('newNotRequiredField', '');
|
||||||
|
obj.set('newNotRequiredFieldWithDefaultValue', null);
|
||||||
|
obj.unset('newRegularField');
|
||||||
|
await obj.save();
|
||||||
|
expect(obj.get('newRequiredField')).toEqual('some value2');
|
||||||
|
expect(obj.get('newRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value2'
|
||||||
|
);
|
||||||
|
expect(obj.get('newNotRequiredField')).toEqual('');
|
||||||
|
expect(obj.get('newNotRequiredFieldWithDefaultValue')).toEqual(null);
|
||||||
|
expect(obj.get('newRegularField')).toEqual(undefined);
|
||||||
|
obj = new Parse.Object('NewClass');
|
||||||
|
obj.set('newRequiredField', 'some value3');
|
||||||
|
obj.set('newRequiredFieldWithDefaultValue', 'some value3');
|
||||||
|
obj.set('newNotRequiredField', 'some value3');
|
||||||
|
obj.set('newNotRequiredFieldWithDefaultValue', 'some value3');
|
||||||
|
obj.set('newRegularField', 'some value3');
|
||||||
|
await obj.save();
|
||||||
|
expect(obj.get('newRequiredField')).toEqual('some value3');
|
||||||
|
expect(obj.get('newRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value3'
|
||||||
|
);
|
||||||
|
expect(obj.get('newNotRequiredField')).toEqual('some value3');
|
||||||
|
expect(obj.get('newNotRequiredFieldWithDefaultValue')).toEqual(
|
||||||
|
'some value3'
|
||||||
|
);
|
||||||
|
expect(obj.get('newRegularField')).toEqual('some value3');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate required fields and set default values after before save trigger', async () => {
|
||||||
|
await request({
|
||||||
|
url: 'http://localhost:8378/1/schemas',
|
||||||
|
method: 'POST',
|
||||||
|
headers: masterKeyHeaders,
|
||||||
|
json: true,
|
||||||
|
body: {
|
||||||
|
className: 'NewClassForBeforeSaveTest',
|
||||||
|
fields: {
|
||||||
|
foo1: { type: 'String' },
|
||||||
|
foo2: { type: 'String', required: true },
|
||||||
|
foo3: {
|
||||||
|
type: 'String',
|
||||||
|
required: true,
|
||||||
|
defaultValue: 'some default value 3',
|
||||||
|
},
|
||||||
|
foo4: { type: 'String', defaultValue: 'some default value 4' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => {
|
||||||
|
req.object.set('foo1', 'some value 1');
|
||||||
|
req.object.set('foo2', 'some value 2');
|
||||||
|
req.object.set('foo3', 'some value 3');
|
||||||
|
req.object.set('foo4', 'some value 4');
|
||||||
|
});
|
||||||
|
|
||||||
|
let obj = new Parse.Object('NewClassForBeforeSaveTest');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
expect(obj.get('foo1')).toEqual('some value 1');
|
||||||
|
expect(obj.get('foo2')).toEqual('some value 2');
|
||||||
|
expect(obj.get('foo3')).toEqual('some value 3');
|
||||||
|
expect(obj.get('foo4')).toEqual('some value 4');
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => {
|
||||||
|
req.object.set('foo1', 'some value 1');
|
||||||
|
req.object.set('foo2', 'some value 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
obj = new Parse.Object('NewClassForBeforeSaveTest');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
expect(obj.get('foo1')).toEqual('some value 1');
|
||||||
|
expect(obj.get('foo2')).toEqual('some value 2');
|
||||||
|
expect(obj.get('foo3')).toEqual('some default value 3');
|
||||||
|
expect(obj.get('foo4')).toEqual('some default value 4');
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => {
|
||||||
|
req.object.set('foo1', 'some value 1');
|
||||||
|
req.object.set('foo2', 'some value 2');
|
||||||
|
req.object.set('foo3', undefined);
|
||||||
|
req.object.unset('foo4');
|
||||||
|
});
|
||||||
|
|
||||||
|
obj = new Parse.Object('NewClassForBeforeSaveTest');
|
||||||
|
obj.set('foo3', 'some value 3');
|
||||||
|
obj.set('foo4', 'some value 4');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
expect(obj.get('foo1')).toEqual('some value 1');
|
||||||
|
expect(obj.get('foo2')).toEqual('some value 2');
|
||||||
|
expect(obj.get('foo3')).toEqual('some default value 3');
|
||||||
|
expect(obj.get('foo4')).toEqual('some default value 4');
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => {
|
||||||
|
req.object.set('foo1', 'some value 1');
|
||||||
|
req.object.set('foo2', undefined);
|
||||||
|
req.object.set('foo3', undefined);
|
||||||
|
req.object.unset('foo4');
|
||||||
|
});
|
||||||
|
|
||||||
|
obj = new Parse.Object('NewClassForBeforeSaveTest');
|
||||||
|
obj.set('foo2', 'some value 2');
|
||||||
|
obj.set('foo3', 'some value 3');
|
||||||
|
obj.set('foo4', 'some value 4');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toEqual('foo2 is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
Parse.Cloud.beforeSave('NewClassForBeforeSaveTest', req => {
|
||||||
|
req.object.set('foo1', 'some value 1');
|
||||||
|
req.object.unset('foo2');
|
||||||
|
req.object.set('foo3', undefined);
|
||||||
|
req.object.unset('foo4');
|
||||||
|
});
|
||||||
|
|
||||||
|
obj = new Parse.Object('NewClassForBeforeSaveTest');
|
||||||
|
obj.set('foo2', 'some value 2');
|
||||||
|
obj.set('foo3', 'some value 3');
|
||||||
|
obj.set('foo4', 'some value 4');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await obj.save();
|
||||||
|
fail('should fail');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toEqual('foo2 is required');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('lets you add fields to system schema', done => {
|
it('lets you add fields to system schema', done => {
|
||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ function mongoSchemaFieldsToParseSchemaFields(schema) {
|
|||||||
);
|
);
|
||||||
var response = fieldNames.reduce((obj, fieldName) => {
|
var response = fieldNames.reduce((obj, fieldName) => {
|
||||||
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]);
|
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]);
|
||||||
|
if (
|
||||||
|
schema._metadata &&
|
||||||
|
schema._metadata.fields_options &&
|
||||||
|
schema._metadata.fields_options[fieldName]
|
||||||
|
) {
|
||||||
|
obj[fieldName] = Object.assign(
|
||||||
|
{},
|
||||||
|
obj[fieldName],
|
||||||
|
schema._metadata.fields_options[fieldName]
|
||||||
|
);
|
||||||
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}, {});
|
}, {});
|
||||||
response.ACL = { type: 'ACL' };
|
response.ACL = { type: 'ACL' };
|
||||||
@@ -212,7 +223,7 @@ class MongoSchemaCollection {
|
|||||||
// Support additional types that Mongo doesn't, like Money, or something.
|
// Support additional types that Mongo doesn't, like Money, or something.
|
||||||
|
|
||||||
// TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint.
|
// TODO: don't spend an extra query on finding the schema if the type we are trying to add isn't a GeoPoint.
|
||||||
addFieldIfNotExists(className: string, fieldName: string, type: string) {
|
addFieldIfNotExists(className: string, fieldName: string, fieldType: string) {
|
||||||
return this._fetchOneSchemaFrom_SCHEMA(className)
|
return this._fetchOneSchemaFrom_SCHEMA(className)
|
||||||
.then(
|
.then(
|
||||||
schema => {
|
schema => {
|
||||||
@@ -221,7 +232,7 @@ class MongoSchemaCollection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The schema exists. Check for existing GeoPoints.
|
// The schema exists. Check for existing GeoPoints.
|
||||||
if (type.type === 'GeoPoint') {
|
if (fieldType.type === 'GeoPoint') {
|
||||||
// Make sure there are not other geopoint fields
|
// Make sure there are not other geopoint fields
|
||||||
if (
|
if (
|
||||||
Object.keys(schema.fields).some(
|
Object.keys(schema.fields).some(
|
||||||
@@ -247,13 +258,37 @@ class MongoSchemaCollection {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
const { type, targetClass, ...fieldOptions } = fieldType;
|
||||||
// We use $exists and $set to avoid overwriting the field type if it
|
// We use $exists and $set to avoid overwriting the field type if it
|
||||||
// already exists. (it could have added inbetween the last query and the update)
|
// already exists. (it could have added inbetween the last query and the update)
|
||||||
return this.upsertSchema(
|
if (fieldOptions && Object.keys(fieldOptions).length > 0) {
|
||||||
className,
|
return this.upsertSchema(
|
||||||
{ [fieldName]: { $exists: false } },
|
className,
|
||||||
{ $set: { [fieldName]: parseFieldTypeToMongoFieldType(type) } }
|
{ [fieldName]: { $exists: false } },
|
||||||
);
|
{
|
||||||
|
$set: {
|
||||||
|
[fieldName]: parseFieldTypeToMongoFieldType({
|
||||||
|
type,
|
||||||
|
targetClass,
|
||||||
|
}),
|
||||||
|
[`_metadata.fields_options.${fieldName}`]: fieldOptions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.upsertSchema(
|
||||||
|
className,
|
||||||
|
{ [fieldName]: { $exists: false } },
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
[fieldName]: parseFieldTypeToMongoFieldType({
|
||||||
|
type,
|
||||||
|
targetClass,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,9 +84,19 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const fieldName in fields) {
|
for (const fieldName in fields) {
|
||||||
|
const { type, targetClass, ...fieldOptions } = fields[fieldName];
|
||||||
mongoObject[
|
mongoObject[
|
||||||
fieldName
|
fieldName
|
||||||
] = MongoSchemaCollection.parseFieldTypeToMongoFieldType(fields[fieldName]);
|
] = MongoSchemaCollection.parseFieldTypeToMongoFieldType({
|
||||||
|
type,
|
||||||
|
targetClass,
|
||||||
|
});
|
||||||
|
if (fieldOptions && Object.keys(fieldOptions).length > 0) {
|
||||||
|
mongoObject._metadata = mongoObject._metadata || {};
|
||||||
|
mongoObject._metadata.fields_options =
|
||||||
|
mongoObject._metadata.fields_options || {};
|
||||||
|
mongoObject._metadata.fields_options[fieldName] = fieldOptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof classLevelPermissions !== 'undefined') {
|
if (typeof classLevelPermissions !== 'undefined') {
|
||||||
@@ -425,6 +435,7 @@ export class MongoStorageAdapter implements StorageAdapter {
|
|||||||
const schemaUpdate = { $unset: {} };
|
const schemaUpdate = { $unset: {} };
|
||||||
fieldNames.forEach(name => {
|
fieldNames.forEach(name => {
|
||||||
schemaUpdate['$unset'][name] = null;
|
schemaUpdate['$unset'][name] = null;
|
||||||
|
schemaUpdate['$unset'][`_metadata.fields_options.${name}`] = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return this._adaptiveCollection(className)
|
return this._adaptiveCollection(className)
|
||||||
|
|||||||
@@ -664,6 +664,13 @@ export default class SchemaController {
|
|||||||
classLevelPermissions
|
classLevelPermissions
|
||||||
);
|
);
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
|
if (validationError instanceof Parse.Error) {
|
||||||
|
return Promise.reject(validationError);
|
||||||
|
} else if (validationError.code && validationError.error) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Parse.Error(validationError.code, validationError.error)
|
||||||
|
);
|
||||||
|
}
|
||||||
return Promise.reject(validationError);
|
return Promise.reject(validationError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,8 +891,23 @@ export default class SchemaController {
|
|||||||
error: 'field ' + fieldName + ' cannot be added',
|
error: 'field ' + fieldName + ' cannot be added',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const error = fieldTypeIsInvalid(fields[fieldName]);
|
const type = fields[fieldName];
|
||||||
|
const error = fieldTypeIsInvalid(type);
|
||||||
if (error) return { code: error.code, error: error.message };
|
if (error) return { code: error.code, error: error.message };
|
||||||
|
if (type.defaultValue !== undefined) {
|
||||||
|
let defaultValueType = getType(type.defaultValue);
|
||||||
|
if (typeof defaultValueType === 'string') {
|
||||||
|
defaultValueType = { type: defaultValueType };
|
||||||
|
}
|
||||||
|
if (!dbTypeMatchesObjectType(type, defaultValueType)) {
|
||||||
|
return {
|
||||||
|
code: Parse.Error.INCORRECT_TYPE,
|
||||||
|
error: `schema mismatch for ${className}.${fieldName} default value; expected ${typeToString(
|
||||||
|
type
|
||||||
|
)} but got ${typeToString(defaultValueType)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -947,7 +969,22 @@ export default class SchemaController {
|
|||||||
|
|
||||||
const expectedType = this.getExpectedType(className, fieldName);
|
const expectedType = this.getExpectedType(className, fieldName);
|
||||||
if (typeof type === 'string') {
|
if (typeof type === 'string') {
|
||||||
type = { type };
|
type = ({ type }: SchemaField);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.defaultValue !== undefined) {
|
||||||
|
let defaultValueType = getType(type.defaultValue);
|
||||||
|
if (typeof defaultValueType === 'string') {
|
||||||
|
defaultValueType = { type: defaultValueType };
|
||||||
|
}
|
||||||
|
if (!dbTypeMatchesObjectType(type, defaultValueType)) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INCORRECT_TYPE,
|
||||||
|
`schema mismatch for ${className}.${fieldName} default value; expected ${typeToString(
|
||||||
|
type
|
||||||
|
)} but got ${typeToString(defaultValueType)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedType) {
|
if (expectedType) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export type LoadSchemaOptions = {
|
|||||||
export type SchemaField = {
|
export type SchemaField = {
|
||||||
type: string,
|
type: string,
|
||||||
targetClass?: ?string,
|
targetClass?: ?string,
|
||||||
|
required?: ?boolean,
|
||||||
|
defaultValue?: ?any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemaFields = { [string]: SchemaField };
|
export type SchemaFields = { [string]: SchemaField };
|
||||||
|
|||||||
@@ -323,16 +323,66 @@ RestWrite.prototype.runBeforeLoginTrigger = async function(userData) {
|
|||||||
|
|
||||||
RestWrite.prototype.setRequiredFieldsIfNeeded = function() {
|
RestWrite.prototype.setRequiredFieldsIfNeeded = function() {
|
||||||
if (this.data) {
|
if (this.data) {
|
||||||
// Add default fields
|
return this.validSchemaController.getAllClasses().then(allClasses => {
|
||||||
this.data.updatedAt = this.updatedAt;
|
const schema = allClasses.find(
|
||||||
if (!this.query) {
|
oneClass => oneClass.className === this.className
|
||||||
this.data.createdAt = this.updatedAt;
|
);
|
||||||
|
const setRequiredFieldIfNeeded = (fieldName, setDefault) => {
|
||||||
|
if (
|
||||||
|
this.data[fieldName] === undefined ||
|
||||||
|
this.data[fieldName] === null ||
|
||||||
|
this.data[fieldName] === '' ||
|
||||||
|
(typeof this.data[fieldName] === 'object' &&
|
||||||
|
this.data[fieldName].__op === 'Delete')
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
setDefault &&
|
||||||
|
schema.fields[fieldName] &&
|
||||||
|
schema.fields[fieldName].defaultValue &&
|
||||||
|
(this.data[fieldName] === undefined ||
|
||||||
|
(typeof this.data[fieldName] === 'object' &&
|
||||||
|
this.data[fieldName].__op === 'Delete'))
|
||||||
|
) {
|
||||||
|
this.data[fieldName] = schema.fields[fieldName].defaultValue;
|
||||||
|
this.storage.fieldsChangedByTrigger =
|
||||||
|
this.storage.fieldsChangedByTrigger || [];
|
||||||
|
if (this.storage.fieldsChangedByTrigger.indexOf(fieldName) < 0) {
|
||||||
|
this.storage.fieldsChangedByTrigger.push(fieldName);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
schema.fields[fieldName] &&
|
||||||
|
schema.fields[fieldName].required === true
|
||||||
|
) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.VALIDATION_ERROR,
|
||||||
|
`${fieldName} is required`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Only assign new objectId if we are creating new object
|
// Add default fields
|
||||||
if (!this.data.objectId) {
|
this.data.updatedAt = this.updatedAt;
|
||||||
this.data.objectId = cryptoUtils.newObjectId(this.config.objectIdSize);
|
if (!this.query) {
|
||||||
|
this.data.createdAt = this.updatedAt;
|
||||||
|
|
||||||
|
// Only assign new objectId if we are creating new object
|
||||||
|
if (!this.data.objectId) {
|
||||||
|
this.data.objectId = cryptoUtils.newObjectId(
|
||||||
|
this.config.objectIdSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (schema) {
|
||||||
|
Object.keys(schema.fields).forEach(fieldName => {
|
||||||
|
setRequiredFieldIfNeeded(fieldName, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (schema) {
|
||||||
|
Object.keys(this.data).forEach(fieldName => {
|
||||||
|
setRequiredFieldIfNeeded(fieldName, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user