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:
Antonio Davi Macedo Coelho de Castro
2019-07-25 21:13:59 -07:00
committed by GitHub
parent d3810c2eba
commit fd637ff4f8
8 changed files with 606 additions and 40 deletions

View File

@@ -188,7 +188,7 @@ describe_only(() => {
const object = new TestObject();
object.set('foo', 'bar');
await object.save();
expect(getSpy.calls.count()).toBe(2);
expect(getSpy.calls.count()).toBe(3);
expect(putSpy.calls.count()).toBe(2);
});
@@ -201,7 +201,7 @@ describe_only(() => {
booleanField: true,
});
await container.save();
expect(getSpy.calls.count()).toBe(2);
expect(getSpy.calls.count()).toBe(3);
expect(putSpy.calls.count()).toBe(2);
});
@@ -215,7 +215,7 @@ describe_only(() => {
object.set('foo', 'barz');
await object.save();
expect(getSpy.calls.count()).toBe(2);
expect(getSpy.calls.count()).toBe(3);
expect(putSpy.calls.count()).toBe(0);
});
@@ -233,7 +233,7 @@ describe_only(() => {
objects.push(object);
}
await Parse.Object.saveAll(objects);
expect(getSpy.calls.count()).toBe(11);
expect(getSpy.calls.count()).toBe(21);
expect(putSpy.calls.count()).toBe(10);
getSpy.calls.reset();
@@ -258,7 +258,7 @@ describe_only(() => {
objects.push(object);
}
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);
getSpy.calls.reset();
@@ -279,7 +279,7 @@ describe_only(() => {
object.set('new', 'barz');
await object.save();
expect(getSpy.calls.count()).toBe(2);
expect(getSpy.calls.count()).toBe(3);
expect(putSpy.calls.count()).toBe(1);
});
@@ -299,7 +299,7 @@ describe_only(() => {
booleanField: true,
});
await object.save();
expect(getSpy.calls.count()).toBe(2);
expect(getSpy.calls.count()).toBe(3);
expect(putSpy.calls.count()).toBe(1);
});
@@ -309,7 +309,7 @@ describe_only(() => {
user.setPassword('testing');
await user.signUp();
expect(getSpy.calls.count()).toBe(6);
expect(getSpy.calls.count()).toBe(8);
expect(putSpy.calls.count()).toBe(1);
});
@@ -326,7 +326,7 @@ describe_only(() => {
object.set('foo', 'bar');
await object.save();
expect(getSpy.calls.count()).toBe(3);
expect(getSpy.calls.count()).toBe(4);
expect(putSpy.calls.count()).toBe(1);
getSpy.calls.reset();

View File

@@ -504,7 +504,7 @@ describe('SchemaController', () => {
schema
.addClassIfNotExists('_InvalidName', { foo: { type: 'String' } })
.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 '
);
done();
@@ -522,7 +522,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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();
});
});
@@ -535,7 +535,7 @@ describe('SchemaController', () => {
)
.catch(error => {
expect(error.code).toEqual(136);
expect(error.error).toEqual('field objectId cannot be added');
expect(error.message).toEqual('field objectId cannot be added');
done();
});
});
@@ -550,7 +550,7 @@ describe('SchemaController', () => {
)
.catch(error => {
expect(error.code).toEqual(136);
expect(error.error).toEqual('field localeIdentifier cannot be added');
expect(error.message).toEqual('field localeIdentifier cannot be added');
done();
});
});
@@ -565,7 +565,7 @@ describe('SchemaController', () => {
)
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
expect(error.error).toEqual('invalid JSON');
expect(error.message).toEqual('invalid JSON');
done();
});
});
@@ -580,7 +580,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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();
});
});
@@ -595,7 +595,7 @@ describe('SchemaController', () => {
)
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
expect(error.error).toEqual('invalid JSON');
expect(error.message).toEqual('invalid JSON');
done();
});
});
@@ -610,7 +610,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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();
});
});
@@ -625,7 +625,7 @@ describe('SchemaController', () => {
)
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
expect(error.error).toEqual('invalid JSON');
expect(error.message).toEqual('invalid JSON');
done();
});
});
@@ -640,7 +640,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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 '
);
done();
@@ -657,7 +657,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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 '
);
done();
@@ -674,7 +674,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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();
});
});
@@ -929,7 +929,7 @@ describe('SchemaController', () => {
)
.catch(error => {
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.'
);
done();

View File

@@ -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 => {
config.database
.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 => {
request({
method: 'POST',