Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions spec/DatabaseController.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,76 @@ describe('DatabaseController', function () {
expect(result2.length).toEqual(1);
});
});

describe('update with validateOnly', () => {
const mockStorageAdapter = {
findOneAndUpdate: () => Promise.resolve({}),
find: () => Promise.resolve([{ objectId: 'test123', testField: 'initialValue' }]),
watch: () => Promise.resolve(),
getAllClasses: () =>
Promise.resolve([
{
className: 'TestObject',
fields: { testField: 'String' },
indexes: {},
classLevelPermissions: { protectedFields: {} },
},
]),
};

it('should use primary readPreference when validateOnly is true', async () => {
const databaseController = new DatabaseController(mockStorageAdapter, {});
const findSpy = spyOn(mockStorageAdapter, 'find').and.callThrough();
const findOneAndUpdateSpy = spyOn(mockStorageAdapter, 'findOneAndUpdate').and.callThrough();

try {
// Call update with validateOnly: true (same as RestWrite.runBeforeSaveTrigger)
await databaseController.update(
'TestObject',
{ objectId: 'test123' },
{ testField: 'newValue' },
{},
true, // skipSanitization: true (matches RestWrite behavior)
true // validateOnly: true
);
} catch (error) {
// validateOnly may throw, but we're checking the find call options
}

// Verify that find was called with primary readPreference
expect(findSpy).toHaveBeenCalled();
const findCall = findSpy.calls.mostRecent();
expect(findCall.args[3]).toEqual({ readPreference: 'primary' }); // options parameter

// Verify that findOneAndUpdate was NOT called (only validation, no actual update)
expect(findOneAndUpdateSpy).not.toHaveBeenCalled();
});

it('should not use primary readPreference when validateOnly is false', async () => {
const databaseController = new DatabaseController(mockStorageAdapter, {});
const findSpy = spyOn(mockStorageAdapter, 'find').and.callThrough();
const findOneAndUpdateSpy = spyOn(mockStorageAdapter, 'findOneAndUpdate').and.callThrough();

try {
// Call update with validateOnly: false
await databaseController.update(
'TestObject',
{ objectId: 'test123' },
{ testField: 'newValue' },
{},
false, // skipSanitization
false // validateOnly
);
} catch (error) {
// May throw for other reasons, but we're checking the call pattern
}

// When validateOnly is false, find should not be called for validation
// Instead, findOneAndUpdate should be called
expect(findSpy).not.toHaveBeenCalled();
expect(findOneAndUpdateSpy).toHaveBeenCalled();
});
});
});

function buildCLP(pointerNames) {
Expand Down
2 changes: 1 addition & 1 deletion src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ class DatabaseController {
convertUsernameToLowercase(update, className, this.options);
transformAuthData(className, update, schema);
if (validateOnly) {
return this.adapter.find(className, schema, query, {}).then(result => {
return this.adapter.find(className, schema, query, { readPreference: 'primary' }).then(result => {
if (!result || !result.length) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
Expand Down