Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
48 changes: 47 additions & 1 deletion packages/e2e-tests/test/e2e-bson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('BSON e2e', function () {
await client.close();
});

describe('printed BSON', function () {
describe.only('printed BSON', function () {
const outputDoc = {
ObjectId: "ObjectId('5f16b8bebe434dc98cdfc9ca')",
DBRef1: "DBRef('a', ObjectId('5f16b8bebe434dc98cdfc9cb'), 'db')",
Expand Down Expand Up @@ -349,6 +349,13 @@ describe('BSON e2e', function () {
expect(await shell.executeLine(value)).to.include(value);
shell.assertNoErrors();
});
it('BinData type 4 prints as UUID when valid', async function () {
const value = "BinData(4, 'ASNFZ4mrze8BI0VniavN7w==')";
expect(await shell.executeLine(value)).to.include(
"UUID('01234567-89ab-cdef-0123-456789abcdef')"
);
shell.assertNoErrors();
});
it('BinData prints as MD5 when created by user as such', async function () {
const value = "MD5('0123456789abcdef0123456789abcdef')";
expect(await shell.executeLine(value)).to.include(value);
Expand Down Expand Up @@ -380,6 +387,45 @@ describe('BSON e2e', function () {
);
shell.assertNoErrors();
});
it('LegacyJavaUUID prints as Binary subtype 3 when created by user', async function () {
const value = 'LegacyJavaUUID()';
const output = await shell.executeLine(value);
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
shell.assertNoErrors();
});
it('LegacyCSharpUUID prints as Binary subtype 3 when created by user', async function () {
const value = 'LegacyCSharpUUID()';
const output = await shell.executeLine(value);
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
shell.assertNoErrors();
});
it('LegacyPythonUUID prints as Binary subtype 3 when created by user', async function () {
const value = 'LegacyPythonUUID()';
const output = await shell.executeLine(value);
expect(output).to.match(/Binary\.createFromBase64\('.+', 3\)/);
shell.assertNoErrors();
});
it('BinData created as LegacyJavaUUID prints as Binary subtype 3', async function () {
const value = "LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef')";
expect(await shell.executeLine(value)).to.include(
"Binary.createFromBase64('782riWdFIwHvzauJZ0UjAQ==', 3)"
);
shell.assertNoErrors();
});
it('BinData created as LegacyCSharpUUID prints as Binary subtype 3', async function () {
const value = "LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')";
expect(await shell.executeLine(value)).to.include(
"Binary.createFromBase64('Z0UjAauJ780BI0VniavN7w==', 3)"
);
shell.assertNoErrors();
});
it('BinData created as LegacyPythonUUID prints as Binary subtype 3', async function () {
const value = "LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')";
expect(await shell.executeLine(value)).to.include(
"Binary.createFromBase64('ASNFZ4mrze8BI0VniavN7w==', 3)"
);
shell.assertNoErrors();
});
});
describe('MaxKey/MinKey special handling', function () {
it('inserts and retrieves MaxKey/MinKey regardless of whether they have been called as functions', async function () {
Expand Down
181 changes: 181 additions & 0 deletions packages/shell-bson/src/shell-bson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,187 @@ describe('Shell BSON', function () {
expect.fail('Expecting error, nothing thrown');
});
});
describe('LegacyJavaUUID', function () {
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
});

it('strips dashes from input', function () {
expect(
shellBson.LegacyJavaUUID('01234567-89ab-cdef-0123-456789abcdef').value()
).to.deep.equal(
shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef').value()
);
});

it('generates a random UUID when no arguments are passed', function () {
const uuid = shellBson.LegacyJavaUUID();
expect(uuid.sub_type).to.equal(3);
expect(uuid.value().length).to.equal(16);
});

it('has help and other metadata', function () {
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
expect(uuid?.help?.type).to.equal('Help');
expect(uuid?.help?.().type).to.equal('Help');
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
});

it('performs byte-swapping for Java compatibility', function () {
// Test that the byte order is swapped correctly for Java UUID format
const uuid = shellBson.LegacyJavaUUID('0123456789abcdef0123456789abcdef');
// The MSB and LSB should be byte-swapped according to Java's UUID format
expect(uuid.toString('hex')).to.equal('efcdab8967452301efcdab8967452301');
});

it('errors for wrong type of arg 1', function () {
try {
(shellBson.LegacyJavaUUID as any)(1);
} catch (e: any) {
return expect(e.message).to.contain('string, got number');
}
expect.fail('Expecting error, nothing thrown');
});
});

describe('LegacyCSharpUUID', function () {
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
const uuid = shellBson.LegacyCSharpUUID(
'0123456789abcdef0123456789abcdef'
);
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
});

it('strips dashes from input', function () {
expect(
shellBson
.LegacyCSharpUUID('01234567-89ab-cdef-0123-456789abcdef')
.value()
).to.deep.equal(
shellBson.LegacyCSharpUUID('0123456789abcdef0123456789abcdef').value()
);
});

it('generates a random UUID when no arguments are passed', function () {
const uuid = shellBson.LegacyCSharpUUID();
expect(uuid.sub_type).to.equal(3);
expect(uuid.value().length).to.equal(16);
});

it('has help and other metadata', function () {
const uuid = shellBson.LegacyCSharpUUID(
'0123456789abcdef0123456789abcdef'
);
expect(uuid?.help?.type).to.equal('Help');
expect(uuid?.help?.().type).to.equal('Help');
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
});

it('performs byte-swapping for C# compatibility', function () {
// Test that the byte order is swapped correctly for C# UUID format
const uuid = shellBson.LegacyCSharpUUID(
'0123456789abcdef0123456789abcdef'
);
// The first three groups should be byte-swapped according to C#'s GUID format
expect(uuid.toString('hex')).to.equal('67452301ab89efcd0123456789abcdef');
});

it('errors for wrong type of arg 1', function () {
try {
(shellBson.LegacyCSharpUUID as any)(1);
} catch (e: any) {
return expect(e.message).to.contain('string, got number');
}
expect.fail('Expecting error, nothing thrown');
});
});

describe('LegacyPythonUUID', function () {
it('creates a Binary with SUBTYPE_UUID_OLD', function () {
const uuid = shellBson.LegacyPythonUUID(
'0123456789abcdef0123456789abcdef'
);
expect(uuid.sub_type).to.equal(3); // SUBTYPE_UUID_OLD
});

it('strips dashes from input', function () {
expect(
shellBson
.LegacyPythonUUID('01234567-89ab-cdef-0123-456789abcdef')
.value()
).to.deep.equal(
shellBson.LegacyPythonUUID('0123456789abcdef0123456789abcdef').value()
);
});

it('generates a random UUID when no arguments are passed', function () {
const uuid = shellBson.LegacyPythonUUID();
expect(uuid.sub_type).to.equal(3);
expect(uuid.value().length).to.equal(16);
});

it('has help and other metadata', function () {
const uuid = shellBson.LegacyPythonUUID(
'0123456789abcdef0123456789abcdef'
);
expect(uuid?.help?.type).to.equal('Help');
expect(uuid?.help?.().type).to.equal('Help');
expect((uuid as any).serverVersions).to.deep.equal(ALL_SERVER_VERSIONS);
});

it('uses standard byte order (no swapping)', function () {
// Python UUID uses standard network byte order (big-endian, no swapping)
const uuid = shellBson.LegacyPythonUUID(
'0123456789abcdef0123456789abcdef'
);
expect(uuid.toString('hex')).to.equal('0123456789abcdef0123456789abcdef');
});

it('errors for wrong type of arg 1', function () {
try {
(shellBson.LegacyPythonUUID as any)(1);
} catch (e: any) {
return expect(e.message).to.contain('string, got number');
}
expect.fail('Expecting error, nothing thrown');
});
});

describe('Legacy UUID comparison', function () {
const testUuid = '0123456789abcdef0123456789abcdef';

it('each legacy UUID type produces different byte representations', function () {
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);

const javaBytes = javaUuid.toString('hex');
const csharpBytes = csharpUuid.toString('hex');
const pythonBytes = pythonUuid.toString('hex');

// They should all be different due to different byte-swapping
expect(javaBytes).to.not.equal(csharpBytes);
expect(javaBytes).to.not.equal(pythonBytes);
expect(csharpBytes).to.not.equal(pythonBytes);

// Python should match the original (no swapping)
expect(pythonBytes).to.equal(testUuid);
});

it('all legacy UUID types use SUBTYPE_UUID_OLD', function () {
const javaUuid = shellBson.LegacyJavaUUID(testUuid);
const csharpUuid = shellBson.LegacyCSharpUUID(testUuid);
const pythonUuid = shellBson.LegacyPythonUUID(testUuid);
const uuid = shellBson.UUID(testUuid);

expect(javaUuid.sub_type).to.equal(3);
expect(csharpUuid.sub_type).to.equal(3);
expect(pythonUuid.sub_type).to.equal(3);
expect(uuid.sub_type).to.equal(4);
});
});

describe('MD5', function () {
let b: any;
let h: any;
Expand Down
89 changes: 89 additions & 0 deletions packages/shell-bson/src/shell-bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ export interface ShellBsonBase<BSONLib extends BSON = BSON> {
) => BSONLib['Binary']['prototype'];
HexData: (subtype: number, hexstr: string) => BSONLib['Binary']['prototype'];
UUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
LegacyJavaUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
LegacyCSharpUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
LegacyPythonUUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
MD5: (hexstr: string) => BSONLib['Binary']['prototype'];
Decimal128: BSONLib['Decimal128'];
BSONSymbol: BSONLib['BSONSymbol'];
Expand Down Expand Up @@ -340,6 +343,92 @@ export function constructShellBson<
},
{ prototype: bson.Binary.prototype }
),

LegacyJavaUUID: assignAll(
function LegacyJavaUUID(hexstr?: string): BinaryType {
if (hexstr === undefined) {
// Generate a new UUID and format it.
hexstr = new bson.UUID().toHexString();
}
assertArgsDefinedType([hexstr], ['string'], 'UUID');

let hex: string = String.prototype.replace.call(
hexstr,
/[{}-]/g,
() => ''
);
let msb = String.prototype.substring.call(hex, 0, 16);
let lsb = String.prototype.substring.call(hex, 16, 32);
msb =
String.prototype.substring.call(msb, 14, 16) +
String.prototype.substring.call(msb, 12, 14) +
String.prototype.substring.call(msb, 10, 12) +
String.prototype.substring.call(msb, 8, 10) +
String.prototype.substring.call(msb, 6, 8) +
String.prototype.substring.call(msb, 4, 6) +
String.prototype.substring.call(msb, 2, 4) +
String.prototype.substring.call(msb, 0, 2);
lsb =
String.prototype.substring.call(lsb, 14, 16) +
String.prototype.substring.call(lsb, 12, 14) +
String.prototype.substring.call(lsb, 10, 12) +
String.prototype.substring.call(lsb, 8, 10) +
String.prototype.substring.call(lsb, 6, 8) +
String.prototype.substring.call(lsb, 4, 6) +
String.prototype.substring.call(lsb, 2, 4) +
String.prototype.substring.call(lsb, 0, 2);
hex = msb + lsb;

const hexBuffer = Buffer.from(hex, 'hex');
return new bson.Binary(hexBuffer, bson.Binary.SUBTYPE_UUID_OLD);
},
{ prototype: bson.Binary.prototype }
),
LegacyCSharpUUID: assignAll(
function LegacyCSharpUUID(hexstr?: string): BinaryType {
if (hexstr === undefined) {
// Generate a new UUID and format it.
hexstr = new bson.UUID().toHexString();
}
assertArgsDefinedType([hexstr], ['string'], 'UUID');

let hex: string = String.prototype.replace.call(
hexstr,
/[{}-]/g,
() => ''
);
const a =
String.prototype.substring.call(hex, 6, 8) +
String.prototype.substring.call(hex, 4, 6) +
String.prototype.substring.call(hex, 2, 4) +
String.prototype.substring.call(hex, 0, 2);
const b =
String.prototype.substring.call(hex, 10, 12) +
String.prototype.substring.call(hex, 8, 10);
const c =
String.prototype.substring.call(hex, 14, 16) +
String.prototype.substring.call(hex, 12, 14);
const d = String.prototype.substring.call(hex, 16, 32);
hex = a + b + c + d;

const hexBuffer = Buffer.from(hex, 'hex');
return new bson.Binary(hexBuffer, bson.Binary.SUBTYPE_UUID_OLD);
},
{ prototype: bson.Binary.prototype }
),
LegacyPythonUUID: assignAll(
function LegacyPythonUUID(hexstr?: string): BinaryType {
if (hexstr === undefined) {
hexstr = new bson.UUID().toString();
}
assertArgsDefinedType([hexstr], ['string'], 'UUID');
// Strip any dashes, as they occur in the standard UUID formatting
// (e.g. 01234567-89ab-cdef-0123-456789abcdef).
const buffer = Buffer.from(hexstr.replace(/-/g, ''), 'hex');
return new bson.Binary(buffer, bson.Binary.SUBTYPE_UUID_OLD);
},
{ prototype: bson.Binary.prototype }
),
MD5: assignAll(
function MD5(hexstr: string): BinaryType {
assertArgsDefinedType([hexstr], ['string'], 'MD5');
Expand Down
Loading