@@ -4,6 +4,7 @@ const Buffer = require('buffer').Buffer;
44const BSON = require ( '../../lib/bson' ) ;
55const Decimal128 = BSON . Decimal128 ;
66const expect = require ( 'chai' ) . expect ;
7+ const EJSON = require ( '../../lib/extended_json' ) ;
78
89var deserializeOptions = {
910 bsonRegExp : true ,
@@ -15,37 +16,123 @@ var serializeOptions = {
1516 ignoreUndefined : false
1617} ;
1718
18- // tests from the corpus that we need to skip, and explanations why
19+ function nativeToBson ( native ) {
20+ const serializeOptions = {
21+ ignoreUndefined : false
22+ } ;
23+
24+ return BSON . serialize ( native , serializeOptions ) ;
25+ }
26+
27+ function bsonToNative ( bson ) {
28+ var deserializeOptions = {
29+ bsonRegExp : true ,
30+ promoteLongs : true ,
31+ promoteValues : false
32+ } ;
33+
34+ return BSON . deserialize ( bson , deserializeOptions ) ;
35+ }
36+
37+ function jsonToNative ( json ) {
38+ return EJSON . parse ( json , { relaxed : false } ) ;
39+ }
1940
20- var skip = {
41+ function nativeToCEJSON ( native ) {
42+ return EJSON . stringify ( native , { relaxed : false } ) ;
43+ }
44+
45+ function nativeToREJSON ( native ) {
46+ return EJSON . stringify ( native , { relaxed : true } ) ;
47+ }
48+
49+ function normalize ( cEJ ) {
50+ return JSON . stringify ( JSON . parse ( cEJ ) ) ;
51+ }
52+
53+ // tests from the corpus that we need to skip, and explanations why
54+ const skipBSON = {
2155 'NaN with payload' :
2256 'passing this would require building a custom type to store the NaN payload data.'
2357} ;
2458
25- const corpus = require ( './tools/bson_corpus_test_loader' ) ;
59+ const skipExtendedJSON = {
60+ 'Timestamp with high-order bit set on both seconds and increment' :
61+ 'Current BSON implementation of timestamp/long cannot hold these values - 1 too large.'
62+ } ;
63+
64+ // test modifications for JavaScript
65+ const modifiedDoubles = {
66+ '+1.0' : { canonical_extjson : '{"d":{"$numberDouble":"1"}}' } ,
67+ '-1.0' : { canonical_extjson : '{"d":{"$numberDouble":"-1"}}' } ,
68+ '1.23456789012345677E+18' : { canonical_extjson : '{"d":{"$numberDouble":"1234567890123456800"}}' } ,
69+ '-1.23456789012345677E+18' : {
70+ canonical_extjson : '{"d":{"$numberDouble":"-1234567890123456800"}}'
71+ } ,
72+ '0.0' : { canonical_extjson : '{"d":{"$numberDouble":"0"}}' } ,
73+ '-0.0' : {
74+ canonical_extjson : '{"d":{"$numberDouble":"0"}}' ,
75+ canonical_bson : '10000000016400000000000000000000'
76+ }
77+ } ;
78+
79+ const modifiedMultitype = {
80+ 'All BSON types' : {
81+ canonical_extjson :
82+ '{"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Symbol":"symbol","String":"string","Int32":{"$numberInt":"42"},"Int64":{"$numberLong":"42"},"Double":{"$numberDouble":"-1"},"Binary":{"$binary":{"base64":"o0w498Or7cijeBSpkquNtg==","subType":"03"}},"BinaryUserDefined":{"$binary":{"base64":"AQIDBAU=","subType":"80"}},"Code":{"$code":"function() {}"},"CodeWithScope":{"$code":"function() {}","$scope":{}},"Subdocument":{"foo":"bar"},"Array":[{"$numberInt":"1"},{"$numberInt":"2"},{"$numberInt":"3"},{"$numberInt":"4"},{"$numberInt":"5"}],"Timestamp":{"$timestamp":{"t":42,"i":1}},"Regex":{"$regularExpression":{"pattern":"pattern","options":""}},"DatetimeEpoch":{"$date":{"$numberLong":"0"}},"DatetimePositive":{"$date":{"$numberLong":"2147483647"}},"DatetimeNegative":{"$date":{"$numberLong":"-2147483648"}},"True":true,"False":false,"DBPointer":{"$ref":"collection","$id":{"$oid":"57e193d7a9cc81b4027498b1"}},"DBRef":{"$ref":"collection","$id":{"$oid":"57fd71e96e32ab4225b723fb"},"$db":"database"},"Minkey":{"$minKey":1},"Maxkey":{"$maxKey":1},"Null":null,"Undefined":null}' ,
83+ canonical_bson :
84+ '48020000075f69640057e193d7a9cc81b4027498b50253796d626f6c000700000073796d626f6c0002537472696e670007000000737472696e670010496e743332002a00000012496e743634002a0000000000000001446f75626c6500000000000000f0bf0542696e617279001000000003a34c38f7c3abedc8a37814a992ab8db60542696e61727955736572446566696e656400050000008001020304050d436f6465000e00000066756e6374696f6e2829207b7d000f436f64655769746853636f7065001b0000000e00000066756e6374696f6e2829207b7d00050000000003537562646f63756d656e74001200000002666f6f0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696d657374616d7000010000002a0000000b5265676578007061747465726e0000094461746574696d6545706f6368000000000000000000094461746574696d65506f73697469766500ffffff7f00000000094461746574696d654e656761746976650000000080ffffffff085472756500010846616c73650000034442506f696e746572002b0000000224726566000b000000636f6c6c656374696f6e00072469640057e193d7a9cc81b4027498b100034442526566003d0000000224726566000b000000636f6c6c656374696f6e00072469640057fd71e96e32ab4225b723fb02246462000900000064617461626173650000ff4d696e6b6579007f4d61786b6579000a4e756c6c000a556e646566696e65640000' ,
85+ converted_extjson :
86+ '{"_id":{"$oid":"57e193d7a9cc81b4027498b5"},"Symbol":"symbol","String":"string","Int32":{"$numberInt":"42"},"Int64":{"$numberLong":"42"},"Double":{"$numberDouble":"-1"},"Binary":{"$binary":{"base64":"o0w498Or7cijeBSpkquNtg==","subType":"03"}},"BinaryUserDefined":{"$binary":{"base64":"AQIDBAU=","subType":"80"}},"Code":{"$code":"function() {}"},"CodeWithScope":{"$code":"function() {}","$scope":{}},"Subdocument":{"foo":"bar"},"Array":[{"$numberInt":"1"},{"$numberInt":"2"},{"$numberInt":"3"},{"$numberInt":"4"},{"$numberInt":"5"}],"Timestamp":{"$timestamp":{"t":42,"i":1}},"Regex":{"$regularExpression":{"pattern":"pattern","options":""}},"DatetimeEpoch":{"$date":{"$numberLong":"0"}},"DatetimePositive":{"$date":{"$numberLong":"2147483647"}},"DatetimeNegative":{"$date":{"$numberLong":"-2147483648"}},"True":true,"False":false,"DBPointer":{"$ref":"collection","$id":{"$oid":"57e193d7a9cc81b4027498b1"}},"DBRef":{"$ref":"collection","$id":{"$oid":"57fd71e96e32ab4225b723fb"},"$db":"database"},"Minkey":{"$minKey":1},"Maxkey":{"$maxKey":1},"Null":null,"Undefined":null}' ,
87+ converted_bson :
88+ '48020000075f69640057e193d7a9cc81b4027498b50253796d626f6c000700000073796d626f6c0002537472696e670007000000737472696e670010496e743332002a00000012496e743634002a0000000000000001446f75626c6500000000000000f0bf0542696e617279001000000003a34c38f7c3abedc8a37814a992ab8db60542696e61727955736572446566696e656400050000008001020304050d436f6465000e00000066756e6374696f6e2829207b7d000f436f64655769746853636f7065001b0000000e00000066756e6374696f6e2829207b7d00050000000003537562646f63756d656e74001200000002666f6f0004000000626172000004417272617900280000001030000100000010310002000000103200030000001033000400000010340005000000001154696d657374616d7000010000002a0000000b5265676578007061747465726e0000094461746574696d6545706f6368000000000000000000094461746574696d65506f73697469766500ffffff7f00000000094461746574696d654e656761746976650000000080ffffffff085472756500010846616c73650000034442506f696e746572002b0000000224726566000b000000636f6c6c656374696f6e00072469640057e193d7a9cc81b4027498b100034442526566003d0000000224726566000b000000636f6c6c656374696f6e00072469640057fd71e96e32ab4225b723fb02246462000900000064617461626173650000ff4d696e6b6579007f4d61786b6579000a4e756c6c000a556e646566696e65640000'
89+ }
90+ } ;
2691
92+ const corpus = require ( './tools/bson_corpus_test_loader' ) ;
2793describe ( 'BSON Corpus' , function ( ) {
2894 corpus . forEach ( scenario => {
29- describe ( scenario . description , function ( ) {
30- if ( scenario . valid ) {
31- describe ( 'valid' , function ( ) {
32- scenario . valid . forEach ( v => {
33- if ( skip . hasOwnProperty ( v . description ) ) {
95+ const deprecated = scenario . deprecated ;
96+ const description = scenario . description ;
97+ const valid = scenario . valid || [ ] ;
98+
99+ // since doubles are formatted differently in JS than in corpus, overwrite expected results
100+ if ( description === 'Double type' ) {
101+ valid . forEach ( v => {
102+ if ( modifiedDoubles [ v . description ] ) {
103+ Object . assign ( v , modifiedDoubles [ v . description ] ) ;
104+ }
105+ } ) ;
106+ // multitype test has a double nested in object, so change those expected values too
107+ } else if ( description === 'Multiple types within the same document' ) {
108+ valid . forEach ( v => {
109+ if ( modifiedMultitype [ v . description ] ) {
110+ Object . assign ( v , modifiedMultitype [ v . description ] ) ;
111+ }
112+ } ) ;
113+ }
114+
115+ describe ( description , function ( ) {
116+ if ( valid ) {
117+ describe ( 'valid-bson' , function ( ) {
118+ valid . forEach ( v => {
119+ if ( skipBSON . hasOwnProperty ( v . description ) ) {
34120 it . skip ( v . description , ( ) => { } ) ;
35121 return ;
36122 }
37123
38124 it ( v . description , function ( ) {
39- var cB = Buffer . from ( v . canonical_bson , 'hex' ) ;
40- if ( v . degenerate_bson ) var dB = Buffer . from ( v . degenerate_bson , 'hex' ) ;
41- if ( v . converted_bson ) var convB = Buffer . from ( v . converted_bson , 'hex' ) ;
125+ const cB = Buffer . from ( v . canonical_bson , 'hex' ) ;
126+ let dB , convB ;
127+ if ( v . degenerate_bson ) dB = Buffer . from ( v . degenerate_bson , 'hex' ) ;
128+ if ( v . converted_bson ) convB = Buffer . from ( v . converted_bson , 'hex' ) ;
42129
43- var roundTripped = BSON . serialize (
130+ const roundTripped = BSON . serialize (
44131 BSON . deserialize ( cB , deserializeOptions ) ,
45132 serializeOptions
46133 ) ;
47134
48- if ( scenario . deprecated ) expect ( convB ) . to . deep . equal ( roundTripped ) ;
135+ if ( deprecated ) expect ( convB ) . to . deep . equal ( roundTripped ) ;
49136 else expect ( cB ) . to . deep . equal ( roundTripped ) ;
50137
51138 if ( dB ) {
@@ -56,6 +143,64 @@ describe('BSON Corpus', function() {
56143 } ) ;
57144 } ) ;
58145 } ) ;
146+
147+ describe ( 'valid-extjson' , function ( ) {
148+ valid . forEach ( v => {
149+ if ( skipExtendedJSON . hasOwnProperty ( v . description ) ) {
150+ it . skip ( v . description , ( ) => { } ) ;
151+ return ;
152+ }
153+
154+ it ( v . description , function ( ) {
155+ // read in test case data. if this scenario is for a deprecated
156+ // type, we want to use the "converted" BSON and EJSON, which
157+ // use the upgraded version of the deprecated type. otherwise,
158+ // just use canonical.
159+ let cB , cEJ ;
160+ if ( deprecated ) {
161+ cB = new Buffer ( v . converted_bson , 'hex' ) ;
162+ cEJ = normalize ( v . converted_extjson ) ;
163+ } else {
164+ cB = new Buffer ( v . canonical_bson , 'hex' ) ;
165+ cEJ = normalize ( v . canonical_extjson ) ;
166+ }
167+
168+ // convert inputs to native Javascript objects
169+ const nativeFromCB = bsonToNative ( cB ) ;
170+
171+ // round tripped EJSON should match the original
172+ expect ( nativeToCEJSON ( jsonToNative ( cEJ ) ) ) . to . equal ( cEJ ) ;
173+
174+ // invalid, but still parseable, EJSON. if provided, make sure that we
175+ // properly convert it to canonical EJSON and BSON.
176+ if ( v . degenerate_extjson ) {
177+ const dEJ = normalize ( v . degenerate_extjson ) ;
178+ const roundTrippedDEJ = nativeToCEJSON ( jsonToNative ( dEJ ) ) ;
179+ expect ( roundTrippedDEJ ) . to . equal ( cEJ ) ;
180+ if ( ! v . lossy ) {
181+ expect ( nativeToBson ( jsonToNative ( dEJ ) ) ) . to . deep . equal ( cB ) ;
182+ }
183+ }
184+
185+ // as long as conversion isn't lossy (i.e. BSON can represent everything in
186+ // the EJSON), make sure EJSON -> native -> BSON matches canonical BSON.
187+ if ( ! v . lossy ) {
188+ expect ( nativeToBson ( jsonToNative ( cEJ ) ) ) . to . deep . equal ( cB ) ;
189+ }
190+
191+ // the reverse direction, BSON -> native -> EJSON, should match canonical EJSON.
192+ expect ( nativeToCEJSON ( nativeFromCB ) ) . to . equal ( cEJ ) ;
193+
194+ if ( v . relaxed_extjson ) {
195+ let rEJ = normalize ( v . relaxed_extjson ) ;
196+ // BSON -> native -> relaxed EJSON matches provided
197+ expect ( nativeToREJSON ( nativeFromCB ) ) . to . equal ( rEJ ) ;
198+ // relaxed EJSON -> native -> relaxed EJSON unchanged
199+ expect ( nativeToREJSON ( jsonToNative ( rEJ ) ) ) . to . equal ( rEJ ) ;
200+ }
201+ } ) ;
202+ } ) ;
203+ } ) ;
59204 }
60205
61206 if ( scenario . decodeErrors ) {
0 commit comments