Skip to content
4 changes: 4 additions & 0 deletions spec/CloudCodeLogger.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ describe('Cloud Code Logger', () => {
spy = spyOn(Config.get('test').loggerController.adapter, 'log').and.callThrough();
const obj = new Parse.Object('TestClass');
await obj.save();
// Wait for afterSave to complete
await new Promise(resolve => setTimeout(resolve, 100));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't await obj.save(); finish only after afterSave completes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should but it seems that cloud logger is performing background actions and the timeout helps


return {
beforeSave: spy.calls
Expand Down Expand Up @@ -391,6 +393,8 @@ describe('Cloud Code Logger', () => {

const obj = new Parse.Object('TestClass');
await obj.save();
// Wait for afterSave to complete
await new Promise(resolve => setTimeout(resolve, 100));
expect(spy).toHaveBeenCalledTimes(0);

const objError = new Parse.Object('TestClassError');
Expand Down
74 changes: 74 additions & 0 deletions spec/ParseRole.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,4 +601,78 @@ describe('Parse Role testing', () => {
});
});
});

it('should trigger afterSave hook when using Parse.Role', async () => {
const afterSavePromise = new Promise(resolve => {
Parse.Cloud.afterSave(Parse.Role, req => {
expect(req.object).toBeDefined();
expect(req.object.get('name')).toBe('AnotherTestRole');
resolve();
});
});

const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
const role = new Parse.Role('AnotherTestRole', acl);

const savedRole = await role.save({}, { useMasterKey: true });
expect(savedRole.id).toBeDefined();

await afterSavePromise;
});

it('should trigger beforeSave hook and allow modifying role in beforeSave', async () => {
Parse.Cloud.beforeSave(Parse.Role, req => {
// Add a custom field in beforeSave
req.object.set('customField', 'addedInBeforeSave');
});

const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
const role = new Parse.Role('ModifiedRole', acl);

const savedRole = await role.save({}, { useMasterKey: true });
expect(savedRole.id).toBeDefined();
expect(savedRole.get('customField')).toBe('addedInBeforeSave');
});

it('should trigger beforeSave hook using Parse.Role', async () => {
let beforeSaveCalled = false;

Parse.Cloud.beforeSave(Parse.Role, req => {
beforeSaveCalled = true;
expect(req.object).toBeDefined();
expect(req.object.get('name')).toBe('BeforeSaveWithClassRef');
});

const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
const role = new Parse.Role('BeforeSaveWithClassRef', acl);

const savedRole = await role.save({}, { useMasterKey: true });
expect(savedRole.id).toBeDefined();
expect(beforeSaveCalled).toBe(true);
});

it('should allow modifying role name in beforeSave hook', async () => {
Parse.Cloud.beforeSave(Parse.Role, req => {
// Modify the role name in beforeSave
if (req.object.get('name') === 'OriginalName') {
req.object.set('name', 'ModifiedName');
}
});

const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
const role = new Parse.Role('OriginalName', acl);

const savedRole = await role.save({}, { useMasterKey: true });
expect(savedRole.id).toBeDefined();
expect(savedRole.get('name')).toBe('ModifiedName');

// Verify the name was actually saved to the database
const query = new Parse.Query(Parse.Role);
const fetchedRole = await query.get(savedRole.id, { useMasterKey: true });
expect(fetchedRole.get('name')).toBe('ModifiedName');
});
});
8 changes: 8 additions & 0 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,14 @@ RestWrite.prototype.buildParseObjects = function () {
const readOnlyAttributes = className.constructor.readOnlyAttributes
? className.constructor.readOnlyAttributes()
: [];

// For _Role class, 'name' cannot be set after the role has an objectId.
// In afterSave context, _handleSaveResponse has already set the objectId,
// so we treat 'name' as read-only to avoid Parse SDK validation errors.
const isRoleAfterSave = this.className === '_Role' && this.response && !this.query;
if (isRoleAfterSave && this.data.name && !readOnlyAttributes.includes('name')) {
readOnlyAttributes.push('name');
}
if (!this.originalData) {
for (const attribute of readOnlyAttributes) {
extraData[attribute] = this.data[attribute];
Expand Down
Loading