'use strict';
* Test dependencies.
*/
const start = require('./common');
const assert = require('assert');
const random = require('./util').random;
const mongoose = start.mongoose;
const CastError = mongoose.SchemaType.CastError;
const DocumentObjectId = mongoose.Types.ObjectId;
const ObjectId = mongoose.Schema.Types.ObjectId;
const Schema = mongoose.Schema;
describe('model query casting', function() {
let Comments;
let BlogPostB;
let geoSchemaArray;
let geoSchemaObject;
let db;
before(function() {
db = start();
});
after(async function() {
await db.close();
});
beforeEach(() => db.deleteModel(/.*/));
afterEach(() => require('./util').clearTestData(db));
afterEach(() => require('./util').stopRemainingOps(db));
beforeEach(function() {
Comments = new Schema();
Comments.add({
title: String,
date: Date,
body: String,
comments: [Comments]
});
const BlogPostSchema = new Schema({
title: { $type: String },
author: String,
slug: String,
date: Date,
meta: {
date: Date,
visitors: Number
},
published: Boolean,
mixed: {},
numbers: [{ $type: Number }],
tags: [String],
sigs: [Buffer],
owners: [ObjectId],
comments: [Comments],
def: { $type: String, default: 'kandinsky' }
}, { typeKey: '$type' });
BlogPostB = db.model('BlogPost', BlogPostSchema);
geoSchemaArray = new Schema({ loc: { type: [Number], index: '2d' } });
geoSchemaObject = new Schema({ loc: { long: Number, lat: Number } });
geoSchemaObject.index({ loc: '2d' });
});
it('works', async function() {
const title = 'Loki ' + random();
const post = new BlogPostB();
const id = post.get('_id').toString();
post.set('title', title);
await post.save();
const doc = await BlogPostB.findOne({ _id: id });
assert.equal(doc.get('title'), title);
});
it('returns cast errors', async function() {
const err = await BlogPostB.find({ date: 'invalid date' }).then(() => null, err => err);
assert.ok(err instanceof Error);
assert.ok(err instanceof CastError);
});
it('casts $modifiers', async function() {
const post = new BlogPostB({
meta: {
visitors: -75
}
});
await post.save();
const found = await BlogPostB.find({ 'meta.visitors': { $gt: '-100', $lt: -50 } });
assert.ok(found);
assert.equal(found.length, 1);
assert.equal(found[0].get('_id').toString(), post.get('_id'));
assert.equal(found[0].get('meta.visitors').valueOf(), post.get('meta.visitors').valueOf());
});
it('casts $in values of arrays (gh-199)', async function() {
const post = new BlogPostB();
const id = post._id.toString();
await post.save();
const doc = await BlogPostB.findOne({ _id: { $in: [id] } });
assert.equal(doc._id.toString(), id);
});
it('casts $in values of arrays with single item instead of array (gh-3238)', async function() {
const post = new BlogPostB();
const id = post._id.toString();
await post.save();
const doc = await BlogPostB.findOne({ _id: { $in: id } });
assert.equal(doc._id.toString(), id);
});
it('casts $nin values of arrays (gh-232)', async function() {
const NinSchema = new Schema({
num: Number
});
const Nin = db.model('Test', NinSchema);
await Nin.create([
{ num: 1 },
{ num: 2 },
{ num: 3 }
]);
const found = await Nin.find({ num: { $nin: [2] } });
assert.equal(found.length, 2);
});
it('works when finding by Date (gh-204)', async function() {
const P = BlogPostB;
const post = new P();
post.meta.date = new Date();
await post.save();
let doc = await P.findOne({ _id: post._id, 'meta.date': { $lte: Date.now() } });
assert.equal(doc._id.toString(), post._id.toString());
doc.meta.date = null;
await doc.save();
doc = await P.findById(doc._id);
assert.strictEqual(doc.meta.date, null);
});
it('works with $type matching', async function() {
const B = BlogPostB;
await B.deleteMany({});
await B.collection.insertMany([{ title: 'test' }, { title: 1 }]);
const err = await B.find({ title: { $type: { x: 1 } } }).then(() => null, err => err);
assert.equal(err.message,
'$type parameter must be number, string, or array of numbers and strings');
let posts = await B.find({ title: { $type: 2 } });
assert.equal(posts.length, 1);
assert.equal(posts[0].title, 'test');
posts = await B.find({ title: { $type: ['string', 'number'] } });
assert.equal(posts.length, 2);
});
it('works when finding Boolean with $in (gh-998)', async function() {
const B = BlogPostB;
const b = new B({ published: true });
await b.save();
const doc = await B.find({ _id: b._id, boolean: { $in: [null, true] } });
assert.ok(doc);
assert.equal(doc[0].id, b.id);
});
it('works when finding Boolean with $ne (gh-1093)', async function() {
const B = BlogPostB;
const b = new B({ published: false });
await b.save();
const doc = await B.find().ne('published', true).exec();
assert.ok(doc);
assert.equal(doc[0].id, b.id);
});
it('properly casts $and (gh-1180)', function() {
const B = BlogPostB;
const result = B.find({}).cast(B, { $and: [{ date: '1987-03-17T20:00:00.000Z' }, { _id: '000000000000000000000000' }] });
assert.ok(result.$and[0].date instanceof Date);
assert.ok(result.$and[1]._id instanceof DocumentObjectId);
});
describe('$near', function() {
this.slow(60);
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $near: ['30', '40'] } });
assert.equal(docs.length, 2);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $near: ['30', '40'], $maxDistance: 51 } });
assert.equal(docs.length, 2);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create(
{ loc: { nested: { long: 10, lat: 20 } } },
{ loc: { nested: { long: 40, lat: 90 } } }
);
const docs = await Test.find({ 'loc.nested': { $near: ['30', '40'], $maxDistance: '50' } });
assert.equal(docs.length, 1);
});
});
describe('$nearSphere', function() {
this.slow(70);
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $nearSphere: ['30', '40'] } });
assert.equal(docs.length, 2);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $nearSphere: ['30', '40'], $maxDistance: 1 } });
assert.equal(docs.length, 2);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { nested: { long: 10, lat: 20 } } }, { loc: { nested: { long: 40, lat: 90 } } });
const docs = await Test.find({ 'loc.nested': { $nearSphere: ['30', '40'], $maxDistance: 1 } });
assert.equal(docs.length, 2);
});
});
describe('$within', function() {
this.slow(60);
describe('$centerSphere', function() {
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $within: { $centerSphere: [['11', '20'], '0.4'] } } });
assert.equal(docs.length, 1);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $within: { $centerSphere: [['11', '20'], '0.4'] } } });
assert.equal(docs.length, 1);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { nested: { long: 10, lat: 20 } } }, { loc: { nested: { long: 40, lat: 90 } } });
const docs = await Test.find({ 'loc.nested': { $within: { $centerSphere: [['11', '20'], '0.4'] } } });
assert.equal(docs.length, 1);
});
});
describe('$center', function() {
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $within: { $center: [['11', '20'], '1'] } } });
assert.equal(docs.length, 1);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $within: { $center: [['11', '20'], '1'] } } });
assert.equal(docs.length, 1);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { nested: { long: 10, lat: 20 } } }, { loc: { nested: { long: 40, lat: 90 } } });
const docs = await Test.find({ 'loc.nested': { $within: { $center: [['11', '20'], '1'] } } });
assert.equal(docs.length, 1);
});
});
describe('$polygon', function() {
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $within: { $polygon: [['8', '1'], ['8', '100'], ['50', '100'], ['50', '1']] } } });
assert.equal(docs.length, 2);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $within: { $polygon: [['8', '1'], ['8', '100'], ['50', '100'], ['50', '1']] } } });
assert.equal(docs.length, 2);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { nested: { long: 10, lat: 20 } } }, { loc: { nested: { long: 40, lat: 90 } } });
const docs = await Test.find({ 'loc.nested': { $within: { $polygon: [['8', '1'], ['8', '100'], ['50', '100'], ['50', '1']] } } });
assert.equal(docs.length, 2);
});
});
describe('$box', function() {
it('with arrays', async function() {
const Test = db.model('Test', geoSchemaArray);
await Test.init();
await Test.create({ loc: [10, 20] }, { loc: [40, 90] });
const docs = await Test.find({ loc: { $within: { $box: [['8', '1'], ['50', '100']] } } });
assert.equal(docs.length, 2);
});
it('with objects', async function() {
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create({ loc: { long: 10, lat: 20 } }, { loc: { long: 40, lat: 90 } });
const docs = await Test.find({ loc: { $within: { $box: [['8', '1'], ['50', '100']] } } });
assert.equal(docs.length, 2);
});
it('with nested objects', async function() {
const geoSchemaObject = new Schema({ loc: { nested: { long: Number, lat: Number } } });
geoSchemaObject.index({ 'loc.nested': '2d' });
const Test = db.model('Test', geoSchemaObject);
await Test.init();
await Test.create(
{ loc: { nested: { long: 10, lat: 20 } } },
{ loc: { nested: { long: 40, lat: 90 } } }
);
const docs = await Test.find({ 'loc.nested': { $within: { $box: [['8', '1'], ['50', '100']] } } });
assert.equal(docs.length, 2);
});
});
});
describe('$options', function() {
it('works on arrays gh-1462', function() {
const opts = {};
opts.toString = function() {
return 'img';
};
const B = BlogPostB;
const result = B.find({}).cast(B, { tags: { $regex: /a/, $options: opts } });
assert.equal(result.tags.$options, 'img');
});
it('does not cast with uppercase (gh-7800)', function() {
const testSchema = new Schema({
name: { type: String, uppercase: true }
});
const Model = db.model('Test', testSchema);
const result = Model.find({}).cast(Model, { name: { $regex: /a/, $options: 'i' } });
assert.equal(result.name.$options, 'i');
});
});
describe('$elemMatch', function() {
it('should cast String to ObjectId in $elemMatch', async function() {
const commentId = new mongoose.Types.ObjectId('1'.repeat(24));
const post = new BlogPostB({ comments: [{ _id: commentId }] });
const id = post._id.toString();
await post.save();
const doc = await BlogPostB.findOne({ _id: id, comments: { $elemMatch: { _id: commentId.toString() } } });
assert.equal(doc._id.toString(), id);
});
it('should cast String to ObjectId in $elemMatch inside $not', async function() {
const commentId = new mongoose.Types.ObjectId('1'.repeat(24));
const post = new BlogPostB({ comments: [{ _id: commentId }] });
const id = post._id.toString();
await post.save();
const doc = await BlogPostB.findOne({
_id: id,
comments: { $not: { $elemMatch: { _id: commentId.toString() } } }
});
assert.equal(doc, null);
});
it('should cast subdoc _id typed as String to String in $elemMatch gh3719', async function() {
const child = new Schema({
_id: { type: String }
}, { _id: false });
const parent = new Schema({
children: [child]
});
const Parent = db.model('Parent', parent);
await Parent.create({ children: [{ _id: 'foobar' }] });
const docs = await Parent.find({
$and: [{ children: { $elemMatch: { _id: 'foobar' } } }]
});
assert.equal(docs.length, 1);
});
it('should cast subdoc _id typed as String to String in $elemMatch inside $not gh3719', async function() {
const child = new Schema({
_id: { type: String }
}, { _id: false });
const parent = new Schema({
children: [child]
});
const Parent = db.model('Parent', parent);
await Parent.create({ children: [{ _id: 'foobar' }] });
const docs = await Parent.find({
$and: [{ children: { $not: { $elemMatch: { _id: 'foobar' } } } }]
});
assert.equal(docs.length, 0);
});
it('casts $nor within $elemMatch (gh-9479)', async function() {
const Test = db.model('Test', Schema({
arr: [{ x: Number, y: Number }]
}));
const _doc = await Test.create({ arr: [{ x: 1 }, { y: 3 }, { x: 2 }] });
const doc = await Test.findOne({
arr: { $elemMatch: { $nor: [{ x: 1 }, { y: 3 }] } }
});
assert.equal(_doc._id.toString(), doc._id.toString());
});
});
it('works with $all (gh-3394)', async function() {
const MyModel = db.model('Test', { tags: [ObjectId] });
const savedDoc = await MyModel.create({
tags: ['00000000000000000000000a', '00000000000000000000000b']
});
assert.equal(typeof savedDoc.tags[0], 'object');
const doc = await MyModel.findOne({ tags: { $all: savedDoc.tags } });
assert.ok(doc);
});
it('date with $not + $type (gh-4632)', async function() {
const MyModel = db.model('Test', { test: Date });
await MyModel.find({ test: { $not: { $type: 9 } } });
});
it('setOnInsert with custom type (gh-5126)', async function() {
function Point(key, options) {
mongoose.SchemaType.call(this, key, options, 'Point');
}
mongoose.Schema.Types.Point = Point;
Point.prototype = Object.create(mongoose.SchemaType.prototype);
let called = 0;
Point.prototype.cast = function(point) {
++called;
if (point.type !== 'Point') {
throw new Error('Woops');
}
return point;
};
const testSchema = new mongoose.Schema({ name: String, test: Point });
const Test = db.model('Test', testSchema);
const u = {
$setOnInsert: {
name: 'a',
test: {
type: 'Point'
}
}
};
await Test.findOneAndUpdate({ name: 'a' }, u).exec();
assert.equal(called, 1);
});
it('lowercase in query (gh-4569)', async function() {
const contexts = [];
const testSchema = new Schema({
name: { type: String, lowercase: true },
num: {
type: Number,
set: function(v) {
contexts.push(this);
return Math.floor(v);
}
}
});
const Test = db.model('Test', testSchema);
return Test.create({ name: 'val', num: 2.02 }).
then(function() {
assert.equal(contexts.length, 1);
assert.equal(contexts[0].constructor.name, 'model');
return Test.findOne({ name: 'VAL' });
}).
then(function(doc) {
assert.ok(doc);
assert.equal(doc.name, 'val');
assert.equal(doc.num, 2);
}).
then(function() {
return Test.findOneAndUpdate({}, { num: 3.14 }, { new: true });
}).
then(function(doc) {
assert.ok(doc);
assert.equal(doc.name, 'val');
assert.equal(doc.num, 3);
assert.equal(contexts.length, 2);
assert.equal(contexts[1].constructor.name, 'Query');
});
});
it('runSettersOnQuery only once on find (gh-5434)', async function() {
let vs = [];
const UserSchema = new mongoose.Schema({
name: String,
foo: {
type: Number,
get: function(val) {
return val.toString();
},
set: function(val) {
vs.push(val);
return val;
}
}
});
const Test = db.model('Test', UserSchema);
await Test.find({ foo: '123' }).exec();
assert.equal(vs.length, 1);
assert.strictEqual(vs[0], '123');
vs = [];
await Test.find({ foo: '123' });
assert.equal(vs.length, 1);
assert.strictEqual(vs[0], '123');
});
it('setters run only once on findOne (gh-6157)', async function() {
let vs = [];
const UserSchema = new mongoose.Schema({
name: String,
foo: {
type: Number,
get: function(val) {
return val.toString();
},
set: function(val) {
vs.push(val);
return val;
}
}
});
const Test = db.model('Test', UserSchema);
await Test.findOne({ foo: '123' });
assert.equal(vs.length, 1);
assert.strictEqual(vs[0], '123');
vs = [];
await Test.findOne({ foo: '123' });
assert.equal(vs.length, 1);
assert.strictEqual(vs[0], '123');
});
it('runSettersOnQuery as query option (gh-5350)', function() {
const contexts = [];
const testSchema = new Schema({
name: { type: String, lowercase: true },
num: {
type: Number,
set: function(v) {
contexts.push(this);
return Math.floor(v);
}
}
});
const Test = db.model('Test', testSchema);
return Test.create({ name: 'val', num: 2.02 }).
then(function() {
assert.equal(contexts.length, 1);
assert.equal(contexts[0].constructor.name, 'model');
return Test.findOne({ name: 'VAL' }, { _id: 0 });
}).
then(function(doc) {
assert.ok(doc);
assert.equal(doc.name, 'val');
assert.equal(doc.num, 2);
});
});
it('_id = 0 (gh-4610)', async function() {
const MyModel = db.model('Test', { _id: Number });
await MyModel.create({ _id: 0 });
const doc = await MyModel.findById({ _id: 0 });
assert.ok(doc);
assert.equal(doc._id, 0);
});
it('converts to CastError (gh-6803)', function() {
const membershipSchema = new Schema({ tier: String });
const schema = new Schema({ membership: membershipSchema, name: String });
const Model = db.model('Test', schema);
return Model.findOne({ membership: '12345' }).
catch(error => {
assert.equal(error.name, 'CastError');
assert.equal(error.path, 'membership');
assert.equal(error.reason.name, 'ObjectParameterError');
});
});
it('minDistance (gh-4197)', async function() {
const schema = new Schema({
name: String,
loc: {
type: { type: String },
coordinates: [Number]
}
});
schema.index({ loc: '2dsphere' });
const MyModel = db.model('Test', schema);
await MyModel.init();
const docs = [
{ name: 'San Mateo Caltrain', loc: _geojsonPoint([-122.33, 37.57]) },
{ name: 'Squaw Valley', loc: _geojsonPoint([-120.24, 39.21]) },
{ name: 'Mammoth Lakes', loc: _geojsonPoint([-118.9, 37.61]) }
];
const RADIUS_OF_EARTH_IN_METERS = 6378100;
await MyModel.create(docs);
const results = await MyModel.
find().
near('loc', {
center: [-122.33, 37.57],
minDistance: (1000 / RADIUS_OF_EARTH_IN_METERS).toString(),
maxDistance: (280000 / RADIUS_OF_EARTH_IN_METERS).toString(),
spherical: true
}).
exec();
assert.equal(results.length, 1);
assert.equal(results[0].name, 'Squaw Valley');
});
it('array ops don\'t break with strict:false (gh-6952)', function() {
const schema = new Schema({}, { strict: false });
const Test = db.model('Test', schema);
return Test.create({ outerArray: [] })
.then(function(created) {
const toBePushedObj = { innerArray: ['onetwothree'] };
const update = { $push: { outerArray: toBePushedObj } };
const opts = { new: true };
return Test.findOneAndUpdate({ _id: created._id }, update, opts);
})
.then(function(updated) {
const doc = updated.toObject();
assert.strictEqual(doc.outerArray[0].innerArray[0], 'onetwothree');
});
});
it('should not throw a cast error when dealing with an array of an array of strings in combination with $elemMatch and $not (gh-13880)', async function() {
const testSchema = new Schema({
arr: [[String]]
});
const Test = db.model('Test', testSchema);
const doc = new Test({ arr: [[1, 2, 3], [2, 3, 4]] });
await doc.save();
const query = { arr: { $elemMatch: { $not: { $elemMatch: { $eq: '1' } } } } };
const res = await Test.find(query);
assert(res);
assert(res[0].arr);
});
it('should not throw a cast error when dealing with an array of objects in combination with $elemMatch (gh-13974)', async function() {
const testSchema = new Schema({
arr: [Object]
});
const Test = db.model('Test', testSchema);
const obj1 = new Test({ arr: [{ id: 'one' }, { id: 'two' }] });
await obj1.save();
const obj2 = new Test({ arr: [{ id: 'two' }, { id: 'three' }] });
await obj2.save();
const obj3 = new Test({ arr: [{ id: 'three' }, { id: 'four' }] });
await obj3.save();
const res = await Test.find({
arr: {
$elemMatch: {
$or: [{ id: 'one' }, { id: 'two' }]
}
}
}).sort({ _id: 1 });
assert.ok(res);
assert.deepStrictEqual(res.map(doc => doc.arr[1].id), ['two', 'three']);
});
it('should not throw a cast error when dealing with an array of objects in combination with $elemMatch and nested $and', async function() {
const testSchema = new Schema({
arr: [Object]
});
const Test = db.model('Test', testSchema);
const obj1 = new Test({ arr: [{ id: 'one', name: 'sample1' }, { id: 'two' }] });
await obj1.save();
const obj2 = new Test({ arr: [{ id: 'two', name: 'sample1' }, { id: 'three' }] });
await obj2.save();
const obj3 = new Test({ arr: [{ id: 'three', name: 'sample1' }, { id: 'four' }] });
await obj3.save();
const res = await Test.find({
arr: {
$elemMatch: {
$and: [
{ name: 'sample1' },
{ $or: [
{ id: 'one' },
{ id: 'two' }
] }
]
}
}
}).sort({ _id: 1 });
assert.ok(res);
assert.equal(res.length, 2);
assert.deepStrictEqual(res.map(doc => doc.arr[1].id), ['two', 'three']);
});
it('should not mutate original object with UUID in query with $elemMatch (gh-15364)', async function() {
const schemaParticipant = new Schema(
{
user: mongoose.Schema.Types.UUID
},
{ _id: false }
);
const TestSchema = new Schema(
{
participants: { type: [schemaParticipant] },
date: { type: Number }
}
);
const Test = db.model('Test', TestSchema);
const peer = {
user: new mongoose.mongo.BSON.UUID('1583b99d-8462-4343-8dfd-9105252e5662')
};
const originalUser = peer.user;
await Test.findOne({ participants: { $elemMatch: peer } });
assert.strictEqual(peer.user, originalUser);
});
});
function _geojsonPoint(coordinates) {
return { type: 'Point', coordinates: coordinates };
}