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
107 changes: 107 additions & 0 deletions spec/MongoSchemaCollectionAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,111 @@ describe('MongoSchemaCollection', () => {
});
done();
});

describe('mongoFieldToParseSchemaField function', () => {
// Test successful type conversions
it('should convert valid mongo field types to parse schema fields', () => {
const testCases = [
{ input: 'string', expected: { type: 'String' } },
{ input: 'number', expected: { type: 'Number' } },
{ input: 'boolean', expected: { type: 'Boolean' } },
{ input: 'date', expected: { type: 'Date' } },
{ input: 'map', expected: { type: 'Object' } },
{ input: 'object', expected: { type: 'Object' } },
{ input: 'array', expected: { type: 'Array' } },
{ input: 'geopoint', expected: { type: 'GeoPoint' } },
{ input: 'file', expected: { type: 'File' } },
{ input: 'bytes', expected: { type: 'Bytes' } },
{ input: 'polygon', expected: { type: 'Polygon' } },
{ input: '*_User', expected: { type: 'Pointer', targetClass: '_User' } },
{ input: '*Post', expected: { type: 'Pointer', targetClass: 'Post' } },
{ input: 'relation<_User>', expected: { type: 'Relation', targetClass: '_User' } },
{ input: 'relation<Post>', expected: { type: 'Relation', targetClass: 'Post' } },
];

testCases.forEach(({ input, expected }) => {
const result = MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: input,
});

expect(result.fields.testField).toEqual(expected);
});
});

// Test error handling for invalid types (non-string values)
it('should throw error for invalid field types', () => {
const invalidInputs = [
null,
undefined,
123,
true,
false,
{},
[],
'',
];

invalidInputs.forEach(invalidInput => {
expect(() => {
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: invalidInput,
});
}).toThrow();
});
});

it('should throw error with correct message for null input', () => {
try {
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: null,
});
} catch (error) {
expect(error.code).toBe(255);
expect(error.message).toContain('Invalid field type');
expect(error.message).toContain('Expected a string');
Copy link
Member

@Moumouls Moumouls Sep 13, 2025

Choose a reason for hiding this comment

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

Issue: When using contain, you can either expect the full error message with equal, or add a contain on the class name and impacted field name to correctly cover the error message 😃.

This way, your feature will not be broken in case of future contributions that touch this area.

Copy link
Author

Choose a reason for hiding this comment

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

I think using toContain makes more sense here, since the error message often includes extra details (like className, fieldName, etc.). A strict would be more fragile.
toContain checks specifically for the className and fieldName would validate the important parts of the message without making the test overly strict.
If this doesn't make sense to you, I can change it.

Copy link
Member

Choose a reason for hiding this comment

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

Hi @rgunindi, the className and field name are important in this kind of error, so I think it’s a good idea to cover them.

This part of the code won’t change often, so having a strict, fragile test is okay. If it becomes too tedious to maintain, it can always be switched back to a simple contain, but I believe it will work fine as is 🙂

}
});

it('should throw error with correct message for undefined input', () => {
try {
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: undefined,
});
} catch (error) {
expect(error.code).toBe(255);
expect(error.message).toContain('Invalid field type');
expect(error.message).toContain('Expected a string');
}
});

it('should throw error with correct message for non-string input', () => {
try {
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: 123,
});
} catch (error) {
expect(error.code).toBe(255);
expect(error.message).toContain('Invalid field type');
expect(error.message).toContain('Expected a string');
}
});

it('should throw error with correct message for empty string input', () => {
try {
MongoSchemaCollection._TESTmongoSchemaToParseSchema({
_id: 'TestClass',
testField: '',
});
} catch (error) {
expect(error.code).toBe(255);
expect(error.message).toContain('Invalid field type');
expect(error.message).toContain('Expected a string');
}
});
});
});
12 changes: 10 additions & 2 deletions src/Adapters/Storage/Mongo/MongoSchemaCollection.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import MongoCollection from './MongoCollection';
import Parse from 'parse/node';

function mongoFieldToParseSchemaField(type) {
function mongoFieldToParseSchemaField(type, fieldName, className) {
// Add type validation to prevent TypeError
if (!type || typeof type !== 'string') {
throw new Parse.Error(
Parse.Error.INVALID_SCHEMA_OPERATION,
`Invalid field type: ${type} for field '${fieldName}' in class '_SCHEMA' (id: ${className}). Expected a string. Fix the type mismatch in your schema configuration.`
);
}

if (type[0] === '*') {
return {
type: 'Pointer',
Expand Down Expand Up @@ -43,7 +51,7 @@ const nonFieldSchemaKeys = ['_id', '_metadata', '_client_permissions'];
function mongoSchemaFieldsToParseSchemaFields(schema) {
var fieldNames = Object.keys(schema).filter(key => nonFieldSchemaKeys.indexOf(key) === -1);
var response = fieldNames.reduce((obj, fieldName) => {
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]);
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName], fieldName, schema._id);
if (
schema._metadata &&
schema._metadata.fields_options &&
Expand Down