Skip to content
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
Loading