@@ -4,6 +4,7 @@ const Buffer = require('buffer').Buffer;
4
4
const BSON = require ( '../../lib/bson' ) ;
5
5
const Decimal128 = BSON . Decimal128 ;
6
6
const expect = require ( 'chai' ) . expect ;
7
+ const EJSON = require ( '../../lib/extended_json' ) ;
7
8
8
9
var deserializeOptions = {
9
10
bsonRegExp : true ,
@@ -15,37 +16,123 @@ var serializeOptions = {
15
16
ignoreUndefined : false
16
17
} ;
17
18
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
+ }
19
40
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 = {
21
55
'NaN with payload' :
22
56
'passing this would require building a custom type to store the NaN payload data.'
23
57
} ;
24
58
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
+ } ;
26
91
92
+ const corpus = require ( './tools/bson_corpus_test_loader' ) ;
27
93
describe ( 'BSON Corpus' , function ( ) {
28
94
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 ) ) {
34
120
it . skip ( v . description , ( ) => { } ) ;
35
121
return ;
36
122
}
37
123
38
124
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' ) ;
42
129
43
- var roundTripped = BSON . serialize (
130
+ const roundTripped = BSON . serialize (
44
131
BSON . deserialize ( cB , deserializeOptions ) ,
45
132
serializeOptions
46
133
) ;
47
134
48
- if ( scenario . deprecated ) expect ( convB ) . to . deep . equal ( roundTripped ) ;
135
+ if ( deprecated ) expect ( convB ) . to . deep . equal ( roundTripped ) ;
49
136
else expect ( cB ) . to . deep . equal ( roundTripped ) ;
50
137
51
138
if ( dB ) {
@@ -56,6 +143,64 @@ describe('BSON Corpus', function() {
56
143
} ) ;
57
144
} ) ;
58
145
} ) ;
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
+ } ) ;
59
204
}
60
205
61
206
if ( scenario . decodeErrors ) {
0 commit comments