Skip to content

Commit d41fb49

Browse files
authored
Merge pull request #15960 from Automattic/fix/gh-15957-nested-map-validation
fix(map): validate map subdocument when loaded with init
2 parents 9949bdb + 459d64f commit d41fb49

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

lib/document.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2824,7 +2824,13 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
28242824

28252825
// Optimization: if primitive path with no validators, or array of primitives
28262826
// with no validators, skip validating this path entirely.
2827-
if (!_pathType.schema && !_pathType.embeddedSchemaType && _pathType.validators.length === 0 && !_pathType.$parentSchemaDocArray) {
2827+
if (!_pathType.schema &&
2828+
!_pathType.embeddedSchemaType &&
2829+
_pathType.validators.length === 0 &&
2830+
!_pathType.$parentSchemaDocArray &&
2831+
// gh-15957: skip this optimization for SchemaMap as maps can contain subdocuments
2832+
// that need validation even if the map itself has no validators
2833+
!_pathType.$isSchemaMap) {
28282834
paths.delete(path);
28292835
} else if (_pathType.$isMongooseArray &&
28302836
!_pathType.$isMongooseDocumentArray && // Skip document arrays...

test/types.map.test.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,4 +1818,68 @@ describe('Map', function() {
18181818
$inc: { __v: 1 }
18191819
});
18201820
});
1821+
1822+
describe('validates nested map subdocuments loaded via init() (gh-15957)', function() {
1823+
it('fails validation for invalid data', async function() {
1824+
// Arrange
1825+
const { company } = createTestContext({ employeeNameMinLength: 2, employeeName: 'X' });
1826+
1827+
// Act
1828+
const error = await company.validate().then(() => null, err => err);
1829+
1830+
// Assert
1831+
assert.ok(error);
1832+
assert.ok(error.errors['teams.engineering.employees.john.name']);
1833+
});
1834+
1835+
it('passes validation for valid data', async function() {
1836+
// Arrange
1837+
const { company } = createTestContext({ employeeNameMinLength: 2, employeeName: 'John' });
1838+
1839+
// Act
1840+
const error = await company.validate().then(() => null, err => err);
1841+
1842+
// Assert
1843+
assert.strictEqual(error, null);
1844+
});
1845+
1846+
it('works with validateSync()', function() {
1847+
// Arrange
1848+
const { company } = createTestContext({ employeeNameMinLength: 2, employeeName: 'X' });
1849+
1850+
// Act
1851+
const error = company.validateSync();
1852+
1853+
// Assert
1854+
assert.ok(error);
1855+
assert.ok(error.errors['teams.engineering.employees.john.name']);
1856+
});
1857+
1858+
function createTestContext({ employeeNameMinLength, employeeName }) {
1859+
const employeeSchema = new Schema({
1860+
name: { type: String, minlength: employeeNameMinLength }
1861+
});
1862+
const teamSchema = new Schema({
1863+
employees: { type: Map, of: employeeSchema }
1864+
});
1865+
const companySchema = new Schema({
1866+
teams: { type: Map, of: teamSchema }
1867+
});
1868+
const Company = db.model('Company', companySchema);
1869+
1870+
const company = new Company();
1871+
company.init({
1872+
_id: new mongoose.Types.ObjectId(),
1873+
teams: {
1874+
engineering: {
1875+
employees: {
1876+
john: { name: employeeName }
1877+
}
1878+
}
1879+
}
1880+
});
1881+
1882+
return { company };
1883+
}
1884+
});
18211885
});

0 commit comments

Comments
 (0)