'use strict';
const start = require('./common');
const util = require('./util');
const assert = require('assert');
const bson = require('mongodb/lib/bson');
const { randomUUID } = require('crypto');
const mongoose = start.mongoose;
const Schema = mongoose.Schema;
const { v4: uuidv4 } = require('uuid');
describe('SchemaUUID', function() {
let Model;
let TestSchema;
let db;
before(async function() {
TestSchema = new Schema({ x: { type: mongoose.Schema.Types.UUID }, y: [{ type: mongoose.Schema.Types.UUID }] });
db = await start().asPromise();
});
after(async function() {
await db.close();
});
beforeEach(async() => {
await db.deleteModel(/.*/);
Model = db.model('Test', TestSchema);
});
afterEach(() => util.clearTestData(db));
afterEach(() => util.stopRemainingOps(db));
it('basic functionality should work', async function() {
const doc = new Model({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' });
assert.ifError(doc.validateSync());
assert.ok(doc.x instanceof mongoose.Types.UUID);
assert.strictEqual(doc.x.toString(), '09190f70-3d30-11e5-8814-0f4df9a59c41');
await doc.save();
const query = Model.findOne({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' });
assert.ok(typeof query._conditions.x === 'string');
const res = await query;
assert.ifError(res.validateSync());
assert.ok(res.x instanceof mongoose.Types.UUID);
assert.strictEqual(res.x.toString(), '09190f70-3d30-11e5-8814-0f4df9a59c41');
const col = db.client.db(db.name).collection(Model.collection.name);
const rawDoc = await col.findOne({ x: new bson.Binary(Buffer.from('09190f703d3011e588140f4df9a59c41', 'hex'), 4) });
assert.ok(rawDoc);
assert.ok(rawDoc.x instanceof bson.Binary);
assert.strictEqual(rawDoc.x.sub_type, 4);
const rawDoc2 = await col.findOne({ x: new bson.UUID('09190f70-3d30-11e5-8814-0f4df9a59c41') });
assert.ok(rawDoc2);
assert.ok(rawDoc2.x instanceof bson.UUID);
assert.strictEqual(rawDoc2.x.sub_type, 4);
});
it('should throw error in case of invalid string', function() {
const doc = new Model({ x: 'invalid' });
const res = doc.validateSync();
assert.ok(res !== null && res !== undefined);
const errors = res.errors;
assert.strictEqual(Object.keys(errors).length, 1);
assert.ok(errors.x instanceof mongoose.Error.CastError);
assert.ok(errors.x.reason.message.includes('not a valid UUID string'), errors.x.reason.message);
});
it('should work with $in and $nin and $all', async function() {
const doc1 = new Model({ y: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34', 'c6f59133-4f84-45a8-bc1d-8f172803e4fe', 'df1309e0-58c5-427a-b22f-6c0fc445ccc0'] });
const doc2 = new Model({ y: ['13d51406-cd06-4fc2-93d1-4fad9b3eecd7', 'f004416b-e02a-4212-ac77-2d3fcf04898b', '5b544b71-8988-422b-a4df-bf691939fe4e'] });
await doc1.save();
await doc2.save();
const foundDocIn = await Model.find({ y: { $in: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34'] } });
assert.ok(foundDocIn);
assert.strictEqual(foundDocIn.length, 1);
assert.ok(foundDocIn[0].y);
assert.strictEqual(foundDocIn[0].y.length, 3);
assert.strictEqual(foundDocIn[0].y[0].toString(), 'f8010af3-bc2c-45e6-85c6-caa30c4a7d34');
assert.strictEqual(foundDocIn[0].y[1].toString(), 'c6f59133-4f84-45a8-bc1d-8f172803e4fe');
assert.strictEqual(foundDocIn[0].y[2].toString(), 'df1309e0-58c5-427a-b22f-6c0fc445ccc0');
const foundDocNin = await Model.find({ y: { $nin: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34'] } });
assert.ok(foundDocNin);
assert.strictEqual(foundDocNin.length, 1);
assert.ok(foundDocNin[0].y);
assert.strictEqual(foundDocNin[0].y.length, 3);
assert.strictEqual(foundDocNin[0].y[0].toString(), '13d51406-cd06-4fc2-93d1-4fad9b3eecd7');
assert.strictEqual(foundDocNin[0].y[1].toString(), 'f004416b-e02a-4212-ac77-2d3fcf04898b');
assert.strictEqual(foundDocNin[0].y[2].toString(), '5b544b71-8988-422b-a4df-bf691939fe4e');
const foundDocAll = await Model.find({ y: { $all: ['13d51406-cd06-4fc2-93d1-4fad9b3eecd7', 'f004416b-e02a-4212-ac77-2d3fcf04898b'] } });
assert.ok(foundDocAll);
assert.strictEqual(foundDocAll.length, 1);
assert.ok(foundDocAll[0].y);
assert.strictEqual(foundDocAll[0].y.length, 3);
assert.strictEqual(foundDocAll[0].y[0].toString(), '13d51406-cd06-4fc2-93d1-4fad9b3eecd7');
assert.strictEqual(foundDocAll[0].y[1].toString(), 'f004416b-e02a-4212-ac77-2d3fcf04898b');
assert.strictEqual(foundDocAll[0].y[2].toString(), '5b544b71-8988-422b-a4df-bf691939fe4e');
});
it('should not convert to string nullish UUIDs (gh-13032)', async function() {
const schema = new Schema({
_id: {
type: Schema.Types.UUID,
default: uuidv4(),
immutable: true
},
name: {
type: String,
required: true
},
organization: {
type: Schema.Types.UUID,
ref: 'Organization',
index: true
}
}, { _id: false });
const Test = db.model('gh_13032', schema);
const { name, organization } = await Test.create({ name: 'test' });
assert.equal(name, 'test');
assert.equal(organization, undefined);
});
it('works with populate (gh-13267)', async function() {
const userSchema = new mongoose.Schema({
_id: { type: 'UUID', default: () => randomUUID() },
name: String,
createdBy: {
type: 'UUID',
ref: 'User'
}
});
const User = db.model('User', userSchema);
const u1 = await User.create({ name: 'admin' });
const { _id } = await User.create({ name: 'created', createdBy: u1._id });
const pop = await User.findById(_id).populate('createdBy').orFail();
assert.equal(pop.createdBy.name, 'admin');
assert.equal(pop.createdBy._id.toString(), u1._id.toString());
await pop.save();
});
it('works with lean', async function() {
const userSchema = new mongoose.Schema({
_id: { type: 'UUID' },
name: String
});
const User = db.model('User', userSchema);
const u1 = await User.create({ _id: randomUUID(), name: 'admin' });
const lean = await User.findById(u1._id).lean().orFail();
assert.equal(lean.name, 'admin');
assert.ok(lean._id instanceof mongoose.Types.UUID);
assert.equal(lean._id.toString(), u1._id.toString());
});
it('handles built-in UUID type (gh-13103)', async function() {
const schema = new Schema({
_id: {
type: Schema.Types.UUID
}
}, { _id: false });
db.deleteModel(/Test/);
const Test = db.model('Test', schema);
const uuid = new mongoose.Types.UUID();
let { _id } = await Test.create({ _id: uuid });
assert.ok(_id);
assert.ok(_id instanceof mongoose.Types.UUID);
assert.equal(_id, uuid.toString());
({ _id } = await Test.findById(uuid));
assert.ok(_id);
assert.ok(_id instanceof mongoose.Types.UUID);
assert.equal(_id, uuid.toString());
});
it('avoids converting maps of uuids to strings (gh-13657)', async function() {
const schema = new mongoose.Schema(
{
doc_map: {
type: mongoose.Schema.Types.Map,
of: mongoose.Schema.Types.UUID
}
}
);
db.deleteModel(/Test/);
const Test = db.model('Test', schema);
await Test.deleteMany({});
const user = new Test({
doc_map: new Map([
['role_1', new mongoose.Types.UUID()],
['role_2', new mongoose.Types.UUID()]
])
});
await user.save();
user.doc_map.set('role_1', new mongoose.Types.UUID());
await user.save();
const exists = await Test.findOne({ 'doc_map.role_1': { $type: 'binData' } });
assert.ok(exists);
assert.ok(user.get('doc_map.role_1') instanceof mongoose.Types.UUID);
});
it('should work with $bits* operators', async function() {
const schema = new Schema({
uuid: mongoose.Schema.Types.UUID
});
db.deleteModel(/Test/);
const Test = db.model('Test', schema);
const uuid = new mongoose.Types.UUID('ff' + '0'.repeat(30));
await Test.create({ uuid });
let doc = await Test.findOne({ uuid: { $bitsAllSet: [0, 4] } });
assert.ok(doc);
doc = await Test.findOne({ uuid: { $bitsAllSet: 2 ** 15 } });
assert.ok(!doc);
doc = await Test.findOne({ uuid: { $bitsAnySet: 3 } });
assert.ok(doc);
doc = await Test.findOne({ uuid: { $bitsAnySet: [8] } });
assert.ok(!doc);
doc = await Test.findOne({ uuid: { $bitsAnyClear: [0, 32] } });
assert.ok(doc);
doc = await Test.findOne({ uuid: { $bitsAnyClear: 7 } });
assert.ok(!doc);
doc = await Test.findOne({ uuid: { $bitsAllClear: [16, 17, 18] } });
assert.ok(doc);
doc = await Test.findOne({ uuid: { $bitsAllClear: 3 } });
assert.ok(!doc);
});
it('should work with $all operator', async function() {
const schema = new Schema({
uuids: [mongoose.Schema.Types.UUID]
});
db.deleteModel(/Test/);
const Test = db.model('Test', schema);
const uuid1 = new mongoose.Types.UUID();
const uuid2 = new mongoose.Types.UUID();
const uuid3 = new mongoose.Types.UUID();
await Test.create({ uuids: [uuid1, uuid2] });
let doc = await Test.findOne({ uuids: { $all: [uuid1, uuid2] } });
assert.ok(doc);
doc = await Test.findOne({ uuids: { $all: [uuid1, uuid3] } });
assert.ok(!doc);
});
});