diff --git a/package-lock.json b/package-lock.json index 1fa1eed5..4b475d51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4409,6 +4409,14 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^3.3.3" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "istanbul-lib-report": { @@ -7223,9 +7231,9 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, "v8-compile-cache": { diff --git a/package.json b/package.json index 932ecdfd..82deb4c1 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "ts-node": "^9.0.0", "typedoc": "^0.18.0", "typescript": "^4.0.2", - "typescript-cached-transpile": "0.0.6" + "typescript-cached-transpile": "0.0.6", + "uuid": "^8.3.2" }, "config": { "native": false diff --git a/src/binary.ts b/src/binary.ts index 6fd898b8..8542d1b9 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -1,7 +1,8 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; +import { uuidHexStringToBuffer } from './uuid_utils'; +import { UUID, UUIDExtended } from './uuid'; import type { EJSONOptions } from './extended_json'; -import { parseUUID, UUIDExtended } from './uuid'; /** @public */ export type BinarySequence = Uint8Array | Buffer | number[]; @@ -228,6 +229,17 @@ export class Binary { }; } + /** @internal */ + toUUID(): UUID { + if (this.sub_type === Binary.SUBTYPE_UUID) { + return new UUID(this.buffer.slice(0, this.position)); + } + + throw new Error( + `Binary sub_type "${this.sub_type}" is not supported for converting to UUID. Only "${Binary.SUBTYPE_UUID}" is currently supported.` + ); + } + /** @internal */ static fromExtendedJSON( doc: BinaryExtendedLegacy | BinaryExtended | UUIDExtended, @@ -248,7 +260,7 @@ export class Binary { } } else if ('$uuid' in doc) { type = 4; - data = Buffer.from(parseUUID(doc.$uuid)); + data = uuidHexStringToBuffer(doc.$uuid); } if (!data) { throw new TypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`); diff --git a/src/bson.ts b/src/bson.ts index a48565de..bcfd65d0 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -12,6 +12,7 @@ import { Map } from './map'; import { MaxKey } from './max_key'; import { MinKey } from './min_key'; import { ObjectId } from './objectid'; +import { UUID } from './uuid'; import { calculateObjectSize as internalCalculateObjectSize } from './parser/calculate_size'; // Parts of the parser import { deserialize as internalDeserialize, DeserializeOptions } from './parser/deserializer'; @@ -81,6 +82,7 @@ export { DBRef, Binary, ObjectId, + UUID, Long, Timestamp, Double, diff --git a/src/objectid.ts b/src/objectid.ts index 9495bf1e..dac7c912 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -227,7 +227,7 @@ export class ObjectId { typeof otherId === 'string' && ObjectId.isValid(otherId) && otherId.length === 12 && - this.id instanceof Buffer + Buffer.isBuffer(this.id) ) { return otherId === this.id.toString('binary'); } @@ -313,7 +313,7 @@ export class ObjectId { return true; } - if (id instanceof Buffer && id.length === 12) { + if (Buffer.isBuffer(id) && id.length === 12) { return true; } diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 905e8aa6..2dff0d54 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,5 +1,7 @@ import { Buffer } from 'buffer'; +type RandomBytesFunction = (size: number) => Uint8Array; + /** * Normalizes our expected stringified form of a function across versions of node * @param fn - The function to stringify @@ -8,11 +10,20 @@ export function normalizedFunctionString(fn: Function): string { return fn.toString().replace('function(', 'function ('); } -function insecureRandomBytes(size: number): Uint8Array { +const isReactNative = + typeof global.navigator === 'object' && global.navigator.product === 'ReactNative'; + +const insecureWarning = isReactNative + ? 'BSON: For React Native please polyfill crypto.getRandomValues, e.g. using: https://www.npmjs.com/package/react-native-get-random-values.' + : 'BSON: No cryptographic implementation for random bytes present, falling back to a less secure implementation.'; + +const insecureRandomBytes: RandomBytesFunction = function insecureRandomBytes(size: number) { + console.warn(insecureWarning); + const result = Buffer.alloc(size); for (let i = 0; i < size; ++i) result[i] = Math.floor(Math.random() * 256); return result; -} +}; /* We do not want to have to include DOM types just for this check */ // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -22,22 +33,34 @@ declare let require: Function; declare let global: any; declare const self: unknown; -export let randomBytes = insecureRandomBytes; -if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { - randomBytes = size => window.crypto.getRandomValues(Buffer.alloc(size)); -} else { +const detectRandomBytes = (): RandomBytesFunction => { + if (typeof window !== 'undefined') { + // browser crypto implementation(s) + const target = window.crypto || window.msCrypto; // allow for IE11 + if (target && target.getRandomValues) { + return size => target.getRandomValues(Buffer.alloc(size)); + } + } + + if (typeof global !== 'undefined' && global.crypto && global.crypto.getRandomValues) { + // allow for RN packages such as https://www.npmjs.com/package/react-native-get-random-values to populate global + return size => global.crypto.getRandomValues(Buffer.alloc(size)); + } + + let requiredRandomBytes: RandomBytesFunction | null | undefined; try { // eslint-disable-next-line @typescript-eslint/no-var-requires - randomBytes = require('crypto').randomBytes; + requiredRandomBytes = require('crypto').randomBytes; } catch (e) { // keep the fallback } // NOTE: in transpiled cases the above require might return null/undefined - if (randomBytes == null) { - randomBytes = insecureRandomBytes; - } -} + + return requiredRandomBytes || insecureRandomBytes; +}; + +export const randomBytes = detectRandomBytes(); export function isUint8Array(value: unknown): value is Uint8Array { return Object.prototype.toString.call(value) === '[object Uint8Array]'; diff --git a/src/uuid.ts b/src/uuid.ts index c5af1119..85288201 100644 --- a/src/uuid.ts +++ b/src/uuid.ts @@ -1,57 +1,212 @@ -/** - * UUID regular expression pattern copied from `uuid` npm module. - * @see https://github.com/uuidjs/uuid/blob/master/src/regex.js - */ -const UUID_RX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; +import { Buffer } from 'buffer'; +import { ensureBuffer } from './ensure_buffer'; +import { Binary } from './binary'; +import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; +import { randomBytes } from './parser/utils'; /** @public */ -export interface UUIDExtended { +export type UUIDExtended = { $uuid: string; -} +}; + +const BYTE_LENGTH = 16; + +const kId = Symbol('id'); /** - * Parser function copied from `uuid` npm module. - * @see https://github.com/uuidjs/uuid/blob/master/src/parse.js - * @internal + * A class representation of the BSON UUID type. + * @public */ -export function parseUUID(uuid: string): Uint8Array { - if (typeof uuid !== 'string') { - throw new TypeError('Invalid type for UUID, expected string but got ' + typeof uuid); +export class UUID { + // This property is not meant for direct serialization, but simply an indication that this type originates from this package. + _bsontype!: 'UUID'; + + static cacheHexString: boolean; + + /** UUID Bytes @internal */ + private [kId]: Buffer; + /** UUID hexString cache @internal */ + private __id?: string; + + /** + * Create an UUID type + * + * @param input - Can be a 32 or 36 character hex string (dashes excluded/included) or a 16 byte binary Buffer. + */ + constructor(input?: string | Buffer | UUID) { + if (typeof input === 'undefined') { + // The most common use case (blank id, new UUID() instance) + this.id = UUID.generate(); + } else if (input instanceof UUID) { + this[kId] = Buffer.from(input.id); + this.__id = input.__id; + } else if ( + (Buffer.isBuffer(input) || ArrayBuffer.isView(input)) && + input.byteLength === BYTE_LENGTH + ) { + this.id = ensureBuffer(input); + } else if (typeof input === 'string') { + this.id = uuidHexStringToBuffer(input); + } else { + throw new TypeError( + 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' + ); + } + } + + /** + * The UUID bytes + * @readonly + */ + get id(): Buffer { + return this[kId]; + } + + set id(value: Buffer) { + this[kId] = value; + + if (UUID.cacheHexString) { + this.__id = bufferToUuidHexString(value); + } + } + + /** + * Generate a 16 byte uuid v4 buffer used in UUIDs + */ + + /** + * Returns the UUID id as a 32 or 36 character hex string representation, excluding/including dashes (defaults to 36 character dash separated) + * @param includeDashes - should the string exclude dash-separators. + * */ + toHexString(includeDashes = true): string { + if (UUID.cacheHexString && this.__id) { + return this.__id; + } + + const uuidHexString = bufferToUuidHexString(this.id, includeDashes); + + if (UUID.cacheHexString) { + this.__id = uuidHexString; + } + + return uuidHexString; } - if (!UUID_RX.test(uuid)) { - throw new TypeError('Invalid format for UUID: ' + uuid); + /** + * Converts the id into a 36 character (dashes included) hex string, unless a encoding is specified. + * @internal + */ + toString(encoding?: string): string { + return encoding ? this.id.toString(encoding) : this.toHexString(); } - let v; - const arr = new Uint8Array(16); + /** + * Converts the id into its JSON string representation. A 36 character (dashes included) hex string in the format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + * @internal + */ + toJSON(): string { + return this.toHexString(); + } + + /** + * Compares the equality of this UUID with `otherID`. + * + * @param otherId - UUID instance to compare against. + */ + equals(otherId: string | Buffer | UUID): boolean { + if (!otherId) { + return false; + } + + if (otherId instanceof UUID) { + return otherId.id.equals(this.id); + } + + try { + return new UUID(otherId).id.equals(this.id); + } catch { + return false; + } + } - // Parse ########-....-....-....-............ - arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24; - arr[1] = (v >>> 16) & 0xff; - arr[2] = (v >>> 8) & 0xff; - arr[3] = v & 0xff; + /** + * Creates a Binary instance from the current UUID. + */ + toBinary(): Binary { + return new Binary(this.id, Binary.SUBTYPE_UUID); + } + + /** + * Generates a populated buffer containing a v4 uuid + */ + static generate(): Buffer { + const bytes = randomBytes(BYTE_LENGTH); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + // Kindly borrowed from https://github.com/uuidjs/uuid/blob/master/src/v4.js + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + return Buffer.from(bytes); + } - // Parse ........-####-....-....-............ - arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8; - arr[5] = v & 0xff; + /** + * Checks if a value is a valid bson UUID + * @param input - UUID, string or Buffer to validate. + */ + static isValid(input: string | Buffer | UUID): boolean { + if (!input) { + return false; + } - // Parse ........-....-####-....-............ - arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8; - arr[7] = v & 0xff; + if (input instanceof UUID) { + return true; + } - // Parse ........-....-....-####-............ - arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8; - arr[9] = v & 0xff; + if (typeof input === 'string') { + return uuidValidateString(input); + } - // Parse ........-....-....-....-############ - // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) - arr[10] = ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff; - arr[11] = (v / 0x100000000) & 0xff; - arr[12] = (v >>> 24) & 0xff; - arr[13] = (v >>> 16) & 0xff; - arr[14] = (v >>> 8) & 0xff; - arr[15] = v & 0xff; + if (Buffer.isBuffer(input)) { + // check for length & uuid version (https://tools.ietf.org/html/rfc4122#section-4.1.3) + if (input.length !== BYTE_LENGTH) { + return false; + } - return arr; + try { + // get this byte as hex: xxxxxxxx-xxxx-XXxx-xxxx-xxxxxxxxxxxx + // check first part as uuid version: xxxxxxxx-xxxx-Xxxx-xxxx-xxxxxxxxxxxx + return parseInt(input[6].toString(16)[0], 10) === Binary.SUBTYPE_UUID; + } catch { + return false; + } + } + + return false; + } + + /** + * Creates an UUID from a hex string representation of an UUID. + * @param hexString - 32 or 36 character hex string (dashes excluded/included). + */ + static createFromHexString(hexString: string): UUID { + const buffer = uuidHexStringToBuffer(hexString); + return new UUID(buffer); + } + + /** + * Converts to a string representation of this Id. + * + * @returns return the 36 character hex string representation. + * @internal + */ + [Symbol.for('nodejs.util.inspect.custom')](): string { + return this.inspect(); + } + + inspect(): string { + return `new UUID("${this.toHexString()}")`; + } } + +Object.defineProperty(UUID.prototype, '_bsontype', { value: 'UUID' }); diff --git a/src/uuid_utils.ts b/src/uuid_utils.ts new file mode 100644 index 00000000..5c5166c2 --- /dev/null +++ b/src/uuid_utils.ts @@ -0,0 +1,31 @@ +import { Buffer } from 'buffer'; + +// Validation regex for v4 uuid (validates with or without dashes) +const VALIDATION_REGEX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15})$/i; + +export const uuidValidateString = (str: string): boolean => + typeof str === 'string' && VALIDATION_REGEX.test(str); + +export const uuidHexStringToBuffer = (hexString: string): Buffer => { + if (!uuidValidateString(hexString)) { + throw new TypeError( + 'UUID string representations must be a 32 or 36 character hex string (dashes excluded/included). Format: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" or "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".' + ); + } + + const sanitizedHexString = hexString.replace(/-/g, ''); + return Buffer.from(sanitizedHexString, 'hex'); +}; + +export const bufferToUuidHexString = (buffer: Buffer, includeDashes = true): string => + includeDashes + ? buffer.toString('hex', 0, 4) + + '-' + + buffer.toString('hex', 4, 6) + + '-' + + buffer.toString('hex', 6, 8) + + '-' + + buffer.toString('hex', 8, 10) + + '-' + + buffer.toString('hex', 10, 16) + : buffer.toString('hex'); diff --git a/test/node/bson_test.js b/test/node/bson_test.js index 406d219d..0fd7dd65 100644 --- a/test/node/bson_test.js +++ b/test/node/bson_test.js @@ -8,6 +8,7 @@ const Binary = BSON.Binary; const Timestamp = BSON.Timestamp; const Long = BSON.Long; const ObjectId = BSON.ObjectId; +const UUID = BSON.UUID; const BSONSymbol = BSON.BSONSymbol; const DBRef = BSON.DBRef; const Decimal128 = BSON.Decimal128; @@ -81,6 +82,7 @@ describe('BSON', function () { it('Should Correctly get BSON types from require', function (done) { var _mongodb = require('../register-bson'); expect(_mongodb.ObjectId === ObjectId).to.be.ok; + expect(_mongodb.UUID === UUID).to.be.ok; expect(_mongodb.Binary === Binary).to.be.ok; expect(_mongodb.Long === Long).to.be.ok; expect(_mongodb.Timestamp === Timestamp).to.be.ok; @@ -687,7 +689,7 @@ describe('BSON', function () { var deserialized = BSON.deserialize(serialized_data, { promoteBuffers: true }); - expect(deserialized.doc instanceof Buffer).to.be.ok; + expect(Buffer.isBuffer(deserialized.doc)).to.be.ok; expect('hello world').to.equal(deserialized.doc.toString()); done(); }); diff --git a/test/node/uuid_tests.js b/test/node/uuid_tests.js new file mode 100644 index 00000000..6598abbe --- /dev/null +++ b/test/node/uuid_tests.js @@ -0,0 +1,163 @@ +'use strict'; + +const { Buffer } = require('buffer'); +const { Binary, UUID } = require('../register-bson'); +const { inspect } = require('util'); +const { validate: uuidStringValidate, version: uuidStringVersion } = require('uuid'); + +// Test values +const UPPERCASE_DASH_SEPARATED_UUID_STRING = 'AAAAAAAA-AAAA-4AAA-AAAA-AAAAAAAAAAAA'; +const UPPERCASE_VALUES_ONLY_UUID_STRING = 'AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAA'; +const LOWERCASE_DASH_SEPARATED_UUID_STRING = 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa'; +const LOWERCASE_VALUES_ONLY_UUID_STRING = 'aaaaaaaaaaaa4aaaaaaaaaaaaaaaaaaa'; + +describe('UUID', () => { + /** + * @ignore + */ + it('should correctly generate a valid UUID v4 from empty constructor', () => { + const uuid = new UUID(); + const uuidHexStr = uuid.toHexString(); + expect(uuidStringValidate(uuidHexStr)).to.be.true; + expect(uuidStringVersion(uuidHexStr)).to.equal(Binary.SUBTYPE_UUID); + }); + + /** + * @ignore + */ + it('should correctly create UUIDs from UPPERCASE & lowercase 36 char dash-separated hex string', () => { + const uuid1 = new UUID(UPPERCASE_DASH_SEPARATED_UUID_STRING); + expect(uuid1.equals(UPPERCASE_DASH_SEPARATED_UUID_STRING)).to.be.true; + expect(uuid1.toString()).to.equal(LOWERCASE_DASH_SEPARATED_UUID_STRING); + + const uuid2 = new UUID(LOWERCASE_DASH_SEPARATED_UUID_STRING); + expect(uuid2.equals(LOWERCASE_DASH_SEPARATED_UUID_STRING)).to.be.true; + expect(uuid2.toString()).to.equal(LOWERCASE_DASH_SEPARATED_UUID_STRING); + }); + + /** + * @ignore + */ + it('should correctly create UUIDs from UPPERCASE & lowercase 32 char hex string (no dash separators)', () => { + const uuid1 = new UUID(UPPERCASE_VALUES_ONLY_UUID_STRING); + expect(uuid1.equals(UPPERCASE_VALUES_ONLY_UUID_STRING)).to.be.true; + expect(uuid1.toHexString(false)).to.equal(LOWERCASE_VALUES_ONLY_UUID_STRING); + + const uuid2 = new UUID(LOWERCASE_VALUES_ONLY_UUID_STRING); + expect(uuid2.equals(LOWERCASE_VALUES_ONLY_UUID_STRING)).to.be.true; + expect(uuid2.toHexString(false)).to.equal(LOWERCASE_VALUES_ONLY_UUID_STRING); + }); + + /** + * @ignore + */ + it('should correctly create UUID from Buffer', () => { + const uuid1 = new UUID(Buffer.from(UPPERCASE_VALUES_ONLY_UUID_STRING, 'hex')); + expect(uuid1.equals(UPPERCASE_DASH_SEPARATED_UUID_STRING)).to.be.true; + expect(uuid1.toString()).to.equal(LOWERCASE_DASH_SEPARATED_UUID_STRING); + + const uuid2 = new UUID(Buffer.from(LOWERCASE_VALUES_ONLY_UUID_STRING, 'hex')); + expect(uuid2.equals(LOWERCASE_DASH_SEPARATED_UUID_STRING)).to.be.true; + expect(uuid2.toString()).to.equal(LOWERCASE_DASH_SEPARATED_UUID_STRING); + }); + + /** + * @ignore + */ + it('should correctly create UUID from UUID (copying existing buffer)', () => { + const org = new UUID(); + const copy = new UUID(org); + expect(org.id).to.not.equal(copy.id); + expect(org.id.equals(copy.id)).to.be.true; + }); + + /** + * @ignore + */ + it('should throw if passed invalid 36-char uuid hex string', () => { + expect(() => new UUID(LOWERCASE_DASH_SEPARATED_UUID_STRING)).to.not.throw(); + expect(() => new UUID('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')).to.throw(TypeError); + // Note: The version is missing here ^ + }); + + /** + * @ignore + */ + it('should throw if passed unsupported argument', () => { + expect(() => new UUID(LOWERCASE_DASH_SEPARATED_UUID_STRING)).to.not.throw(); + expect(() => new UUID({})).to.throw(TypeError); + }); + + /** + * @ignore + */ + it('should correctly check if a buffer isValid', () => { + const validBuffer = Buffer.from(UPPERCASE_VALUES_ONLY_UUID_STRING, 'hex'); + const invalidBuffer1 = Buffer.from('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'hex'); + const invalidBuffer2 = Buffer.alloc(16); + + expect(validBuffer.length).to.equal(invalidBuffer1.length); + expect(validBuffer.length).to.equal(invalidBuffer2.length); + expect(UUID.isValid(invalidBuffer1)).to.be.false; + expect(UUID.isValid(invalidBuffer2)).to.be.false; + expect(UUID.isValid(validBuffer)).to.be.true; + }); + + /** + * @ignore + */ + it('should correctly convert to and from a Binary instance', () => { + const uuid = new UUID(LOWERCASE_DASH_SEPARATED_UUID_STRING); + expect(UUID.isValid(uuid)).to.be.true; + + const bin = uuid.toBinary(); + expect(bin).to.be.instanceOf(Binary); + + const uuid2 = bin.toUUID(); + expect(uuid2.toHexString()).to.equal(LOWERCASE_DASH_SEPARATED_UUID_STRING); + }); + + /** + * @ignore + */ + it('should correctly convert to and from a Binary instance', () => { + const uuid = new UUID(LOWERCASE_DASH_SEPARATED_UUID_STRING); + expect(UUID.isValid(uuid)).to.be.true; + + const bin = uuid.toBinary(); + expect(bin).to.be.instanceOf(Binary); + + const uuid2 = bin.toUUID(); + expect(uuid.equals(uuid2)).to.be.true; + }); + + /** + * @ignore + */ + it('should throw when converted from an incompatible Binary instance', () => { + const validRandomBuffer = Buffer.from('Hello World!'); + const binRand = new Binary(validRandomBuffer); + + expect(() => binRand.toUUID()).to.throw(); + + const validUuidV3String = '25f0d698-15b9-3a7a-96b1-a573061e29c9'; + const validUuidV3Buffer = Buffer.from(validUuidV3String.replace(/-/g, ''), 'hex'); + const binV3 = new Binary(validUuidV3Buffer, Binary.SUBTYPE_UUID_OLD); + + expect(() => binV3.toUUID()).to.throw(); + + const validUuidV4String = 'bd2d74fe-bad8-430c-aeac-b01d073a1eb6'; + const validUuidV4Buffer = Buffer.from(validUuidV4String.replace(/-/g, ''), 'hex'); + const binV4 = new Binary(validUuidV4Buffer, Binary.SUBTYPE_UUID); + + expect(() => binV4.toUUID()).to.not.throw(); + }); + + /** + * @ignore + */ + it('should correctly allow for node.js inspect to work with UUID', () => { + const uuid = new UUID(UPPERCASE_DASH_SEPARATED_UUID_STRING); + expect(inspect(uuid)).to.equal(`new UUID("${LOWERCASE_DASH_SEPARATED_UUID_STRING}")`); + }); +});