1
+ import * as util from 'util' ;
1
2
import * as fs from 'fs' ;
2
3
import * as path from 'path' ;
3
- import { BSON , BSONError , Binary } from '../register-bson' ;
4
+ import { BSON , BSONError , Binary , EJSON } from '../register-bson' ;
4
5
import { expect } from 'chai' ;
5
6
6
7
const { toHex, fromHex } = BSON . onDemand . ByteUtils ;
7
8
8
9
type VectorHexType = '0x03' | '0x27' | '0x10' ;
9
10
type VectorTest = {
10
11
description : string ;
11
- vector : ( number | string ) [ ] ;
12
+ vector ?: number [ ] ;
12
13
valid : boolean ;
13
14
dtype_hex : VectorHexType ;
14
15
padding ?: number ;
@@ -17,15 +18,11 @@ type VectorTest = {
17
18
type VectorSuite = { description : string ; test_key : string ; tests : VectorTest [ ] } ;
18
19
19
20
function fixFloats ( f : string | number ) : number {
21
+ // Should be nothing to "fix" but validates we didn't get
22
+ // an unexpected type so we don't silently fail on it during the test
20
23
if ( typeof f === 'number' ) {
21
24
return f ;
22
25
}
23
- if ( f === 'inf' ) {
24
- return Infinity ;
25
- }
26
- if ( f === '-inf' ) {
27
- return - Infinity ;
28
- }
29
26
throw new Error ( `test format error: unknown float value: ${ f } ` ) ;
30
27
}
31
28
@@ -49,7 +46,20 @@ function fixBits(f: number | string): number {
49
46
return f ;
50
47
}
51
48
52
- function make ( vector : ( number | string ) [ ] , dtype_hex : VectorHexType , padding ?: number ) : Binary {
49
+ function dtypeToHelper ( dtype_hex : string ) {
50
+ switch ( dtype_hex ) {
51
+ case '0x10' /* packed_bit */ :
52
+ return 'fromPackedBits' ;
53
+ case '0x03' /* int8 */ :
54
+ return 'fromInt8Array' ;
55
+ case '0x27' /* float32 */ :
56
+ return 'fromFloat32Array' ;
57
+ default :
58
+ throw new Error ( `Unknown dtype_hex: ${ dtype_hex } ` ) ;
59
+ }
60
+ }
61
+
62
+ function make ( vector : number [ ] , dtype_hex : VectorHexType , padding ?: number ) : Binary {
53
63
let binary : Binary ;
54
64
switch ( dtype_hex ) {
55
65
case '0x10' /* packed_bit */ :
@@ -87,21 +97,152 @@ const invalidTestExpectedError = new Map()
87
97
'Invalid Vector: padding must be a value between 0 and 7'
88
98
)
89
99
. set ( 'Negative padding PACKED_BIT' , 'Invalid Vector: padding must be a value between 0 and 7' )
90
- // skipped
91
- . set ( 'Overflow Vector PACKED_BIT' , false )
92
- . set ( 'Underflow Vector PACKED_BIT' , false )
93
- . set ( 'Overflow Vector INT8' , false )
94
- . set ( 'Underflow Vector INT8' , false )
95
- . set ( 'INT8 with float inputs' , false )
96
- // duplicate test! but also skipped.
97
- . set ( 'Vector with float values PACKED_BIT' , false )
98
- . set ( 'Vector with float values PACKED_BIT' , false ) ;
100
+ . set (
101
+ 'Insufficient vector data FLOAT32' ,
102
+ 'Invalid Vector: Float32 vector must contain a multiple of 4 bytes'
103
+ )
104
+ // These are not possible given the constraints of the input types allowed:
105
+ // our helpers will throw an "unsupported_error" for these
106
+ . set ( 'Overflow Vector PACKED_BIT' , 'unsupported_error' )
107
+ . set ( 'Underflow Vector PACKED_BIT' , 'unsupported_error' )
108
+ . set ( 'Overflow Vector INT8' , 'unsupported_error' )
109
+ . set ( 'Underflow Vector INT8' , 'unsupported_error' )
110
+ . set ( 'INT8 with float inputs' , 'unsupported_error' )
111
+ . set ( 'Vector with float values PACKED_BIT' , 'unsupported_error' ) ;
112
+
113
+ function catchError < T > (
114
+ fn : ( ) => T
115
+ ) : { status : 'returned' ; result : T } | { status : 'thrown' ; result : Error } {
116
+ try {
117
+ return { status : 'returned' , result : fn ( ) } ;
118
+ } catch ( error ) {
119
+ return { status : 'thrown' , result : error } ;
120
+ }
121
+ }
122
+
123
+ function testVectorInvalidInputValues ( test : VectorTest , expectedErrorMessage : string ) {
124
+ const binaryCreation = catchError ( make . bind ( null , test . vector ! , test . dtype_hex , test . padding ) ) ;
125
+ const bsonBytesCreation =
126
+ binaryCreation . status !== 'thrown'
127
+ ? catchError ( BSON . serialize . bind ( null , { bin : binaryCreation . result } ) )
128
+ : undefined ;
129
+ const ejsonStringCreation =
130
+ binaryCreation . status !== 'thrown'
131
+ ? catchError ( BSON . EJSON . stringify . bind ( null , { bin : binaryCreation . result } ) )
132
+ : undefined ;
133
+
134
+ const binaryHelperValidations = [
135
+ 'Padding specified with no vector data PACKED_BIT' ,
136
+ 'Exceeding maximum padding PACKED_BIT' ,
137
+ 'Negative padding PACKED_BIT' ,
138
+ ...Array . from ( invalidTestExpectedError . entries ( ) )
139
+ . filter ( ( [ , v ] ) => v === 'unsupported_error' )
140
+ . map ( ( [ k ] ) => k )
141
+ ] ;
142
+
143
+ const errorType = expectedErrorMessage === 'unsupported_error' ? Error : BSONError ;
144
+ const errorName = expectedErrorMessage === 'unsupported_error' ? 'Error' : 'BSONError' ;
145
+
146
+ const check = outcome => {
147
+ expect ( outcome ) . to . exist ;
148
+ expect ( outcome . status ) . to . equal ( 'thrown' ) ;
149
+ expect ( outcome . result ) . to . be . instanceOf ( errorType ) ;
150
+ expect ( outcome . result ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
151
+ } ;
152
+
153
+ if ( binaryHelperValidations . includes ( test . description ) ) {
154
+ describe ( 'when creating a BSON Vector given invalid input values' , ( ) => {
155
+ it ( `Binary.${ dtypeToHelper ( test . dtype_hex ) } () throws a ${ errorName } ` , function ( ) {
156
+ check ( binaryCreation ) ;
157
+ } ) ;
158
+ } ) ;
159
+ } else {
160
+ expect ( errorName ) . to . equal ( 'BSONError' ) ; // unsupported_error are only when making vectors
161
+
162
+ describe ( 'when encoding a BSON Vector given invalid input values' , ( ) => {
163
+ it ( `Binary.${ dtypeToHelper ( test . dtype_hex ) } () does not throw` , function ( ) {
164
+ expect ( binaryCreation ) . to . have . property ( 'status' , 'returned' ) ;
165
+ } ) ;
166
+
167
+ it ( `BSON.serialize() throws a BSONError` , function ( ) {
168
+ check ( bsonBytesCreation ) ;
169
+ } ) ;
170
+
171
+ it ( `EJSON.stringify() throws a BSONError` , function ( ) {
172
+ check ( ejsonStringCreation ) ;
173
+ } ) ;
174
+ } ) ;
175
+ }
176
+ }
177
+
178
+ function testVectorInvalidBSONBytes ( test : VectorTest , expectedErrorMessage : string ) {
179
+ describe ( 'when encoding a Binary Vector made from invalid bytes' , ( ) => {
180
+ it ( `BSON.serialize() throw a BSONError` , function ( ) {
181
+ let thrownError : Error | undefined ;
182
+ const bin = BSON . deserialize ( Buffer . from ( test . canonical_bson ! , 'hex' ) ) ;
183
+
184
+ try {
185
+ BSON . serialize ( bin ) ;
186
+ } catch ( error ) {
187
+ thrownError = error ;
188
+ }
189
+
190
+ expect ( thrownError , thrownError ?. stack ) . to . be . instanceOf ( BSONError ) ;
191
+ expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
192
+ } ) ;
193
+
194
+ const toHelper = dtypeToHelper ( test . dtype_hex ) . replace ( 'from' , 'to' ) ;
195
+ it ( `Binary.${ toHelper } () throw a BSONError` , function ( ) {
196
+ let thrownError : Error | undefined ;
197
+ const bin = BSON . deserialize ( Buffer . from ( test . canonical_bson ! , 'hex' ) ) ;
198
+
199
+ try {
200
+ bin . vector [ toHelper ] ( ) ;
201
+ } catch ( error ) {
202
+ thrownError = error ;
203
+ }
204
+
205
+ expect ( thrownError , thrownError ?. stack ) . to . be . instanceOf ( BSONError ) ;
206
+ expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
207
+ } ) ;
208
+
209
+ if ( toHelper === 'toPackedBits' ) {
210
+ it ( `Binary.toBits() throw a BSONError` , function ( ) {
211
+ let thrownError : Error | undefined ;
212
+ const bin = BSON . deserialize ( Buffer . from ( test . canonical_bson ! , 'hex' ) ) ;
213
+
214
+ try {
215
+ bin . vector . toBits ( ) ;
216
+ } catch ( error ) {
217
+ thrownError = error ;
218
+ }
219
+
220
+ expect ( thrownError , thrownError ?. stack ) . to . be . instanceOf ( BSONError ) ;
221
+ expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
222
+ } ) ;
223
+ }
224
+
225
+ it ( `EJSON.stringify() throw a BSONError` , function ( ) {
226
+ let thrownError : Error | undefined ;
227
+ const bin = BSON . deserialize ( Buffer . from ( test . canonical_bson ! , 'hex' ) ) ;
228
+
229
+ try {
230
+ EJSON . stringify ( bin ) ;
231
+ } catch ( error ) {
232
+ thrownError = error ;
233
+ }
234
+
235
+ expect ( thrownError , thrownError ?. stack ) . to . be . instanceOf ( BSONError ) ;
236
+ expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
237
+ } ) ;
238
+ } ) ;
239
+ }
99
240
100
241
describe ( 'BSON Binary Vector spec tests' , ( ) => {
101
242
const tests : Record < string , VectorSuite > = Object . create ( null ) ;
102
243
103
244
for ( const file of fs . readdirSync ( path . join ( __dirname , 'specs/bson-binary-vector' ) ) ) {
104
- tests [ path . basename ( file , '.json' ) ] = JSON . parse (
245
+ tests [ path . basename ( file , '.json' ) ] = EJSON . parse (
105
246
fs . readFileSync ( path . join ( __dirname , 'specs/bson-binary-vector' , file ) , 'utf8' )
106
247
) ;
107
248
}
@@ -120,20 +261,22 @@ describe('BSON Binary Vector spec tests', () => {
120
261
* > MUST assert that the input float array is the same after encoding and decoding.
121
262
*/
122
263
for ( const test of valid ) {
123
- it ( `encode ${ test . description } ` , function ( ) {
124
- const bin = make ( test . vector , test . dtype_hex , test . padding ) ;
264
+ describe ( test . description , ( ) => {
265
+ it ( `calling Binary.${ dtypeToHelper ( test . dtype_hex ) } () with input numbers and serializing it does not throw` , function ( ) {
266
+ const bin = make ( test . vector ! , test . dtype_hex , test . padding ) ;
125
267
126
- const buffer = BSON . serialize ( { [ suite . test_key ] : bin } ) ;
127
- expect ( toHex ( buffer ) ) . to . equal ( test . canonical_bson ! . toLowerCase ( ) ) ;
128
- } ) ;
268
+ const buffer = BSON . serialize ( { [ suite . test_key ] : bin } ) ;
269
+ expect ( toHex ( buffer ) ) . to . equal ( test . canonical_bson ! . toLowerCase ( ) ) ;
270
+ } ) ;
129
271
130
- it ( `decode ${ test . description } ` , function ( ) {
131
- const canonical_bson = fromHex ( test . canonical_bson ! . toLowerCase ( ) ) ;
132
- const doc = BSON . deserialize ( canonical_bson ) ;
272
+ it ( `creating a Binary instance from BSON bytes does not throw ` , function ( ) {
273
+ const canonical_bson = fromHex ( test . canonical_bson ! . toLowerCase ( ) ) ;
274
+ const doc = BSON . deserialize ( canonical_bson ) ;
133
275
134
- expect ( doc [ suite . test_key ] . sub_type ) . to . equal ( 0x09 ) ;
135
- expect ( doc [ suite . test_key ] . buffer [ 0 ] ) . to . equal ( + test . dtype_hex ) ;
136
- expect ( doc [ suite . test_key ] . buffer [ 1 ] ) . to . equal ( test . padding ) ;
276
+ expect ( doc [ suite . test_key ] . sub_type ) . to . equal ( 0x09 ) ;
277
+ expect ( doc [ suite . test_key ] . buffer [ 0 ] ) . to . equal ( + test . dtype_hex ) ;
278
+ expect ( doc [ suite . test_key ] . buffer [ 1 ] ) . to . equal ( test . padding ) ;
279
+ } ) ;
137
280
} ) ;
138
281
}
139
282
} ) ;
@@ -147,46 +290,18 @@ describe('BSON Binary Vector spec tests', () => {
147
290
for ( const test of invalid ) {
148
291
const expectedErrorMessage = invalidTestExpectedError . get ( test . description ) ;
149
292
150
- it ( `bson: ${ test . description } ` , function ( ) {
151
- let thrownError : Error | undefined ;
152
- try {
153
- const bin = make ( test . vector , test . dtype_hex , test . padding ) ;
154
- BSON . serialize ( { bin } ) ;
155
- } catch ( error ) {
156
- thrownError = error ;
293
+ describe ( test . description , ( ) => {
294
+ if ( test . canonical_bson != null ) {
295
+ testVectorInvalidBSONBytes ( test , expectedErrorMessage ) ;
157
296
}
158
297
159
- if ( thrownError ?. message . startsWith ( 'unsupported_error' ) ) {
160
- expect (
161
- expectedErrorMessage ,
162
- 'We expect a certain error message but got an unsupported error'
163
- ) . to . be . false ;
164
- this . skip ( ) ;
298
+ if ( test . vector != null ) {
299
+ testVectorInvalidInputValues ( test , expectedErrorMessage ) ;
165
300
}
166
301
167
- expect ( thrownError ) . to . be . instanceOf ( BSONError ) ;
168
- expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
169
- } ) ;
170
-
171
- it ( `extended json: ${ test . description } ` , function ( ) {
172
- let thrownError : Error | undefined ;
173
- try {
174
- const bin = make ( test . vector , test . dtype_hex , test . padding ) ;
175
- BSON . EJSON . stringify ( { bin } ) ;
176
- } catch ( error ) {
177
- thrownError = error ;
302
+ if ( test . vector == null && test . canonical_bson == null ) {
303
+ throw new Error ( 'not testing anything for: ' + util . inspect ( test ) ) ;
178
304
}
179
-
180
- if ( thrownError ?. message . startsWith ( 'unsupported_error' ) ) {
181
- expect (
182
- expectedErrorMessage ,
183
- 'We expect a certain error message but got an unsupported error'
184
- ) . to . be . false ;
185
- this . skip ( ) ;
186
- }
187
-
188
- expect ( thrownError ) . to . be . instanceOf ( BSONError ) ;
189
- expect ( thrownError ?. message ) . to . match ( new RegExp ( expectedErrorMessage ) ) ;
190
305
} ) ;
191
306
}
192
307
} ) ;
0 commit comments