@@ -9,14 +9,17 @@ package bsondump
99import (
1010 "bytes"
1111 "crypto/rand"
12+ "fmt"
1213 "os"
1314 "os/exec"
1415 "path/filepath"
16+ "regexp"
1517 "strings"
1618 "testing"
1719
1820 "github.com/mongodb/mongo-tools/common/testtype"
1921 "github.com/mongodb/mongo-tools/common/testutil"
22+ "github.com/stretchr/testify/assert"
2023 "github.com/stretchr/testify/require"
2124 "go.mongodb.org/mongo-driver/v2/bson"
2225)
@@ -260,6 +263,197 @@ func testFromFileWithPositionalArgumentToFile(t *testing.T) {
260263 require .Equal (bufRefStr , bufDumpStr )
261264}
262265
266+ // TestBsondumpAllTypesDebug verifies that bsondump --type=debug outputs the correct BSON type
267+ // numbers for all non-deprecated BSON types (from all_types.js).
268+ func TestBsondumpAllTypesDebug (t * testing.T ) {
269+ testtype .SkipUnlessTestType (t , testtype .UnitTestType )
270+
271+ out , err := runBsondump ("--type=debug" , "testdata/all_types.bson" )
272+ require .NoError (t , err , "bsondump should exit successfully with --type=debug" )
273+
274+ assert .Equal (
275+ t ,
276+ 22 ,
277+ strings .Count (out , "--- new object ---" ),
278+ "should find all 22 documents in all_types.bson" ,
279+ )
280+
281+ for _ , typeNum := range []int {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 17 , 18 , - 1 , 127 } {
282+ re := regexp .MustCompile (fmt .Sprintf (`type:\s+%d` , typeNum ))
283+ assert .Regexp (t , re , out , "expected type %d in debug output" , typeNum )
284+ }
285+ }
286+
287+ // TestBsondumpAllTypesJSON verifies that bsondump --type=json correctly serializes all
288+ // non-deprecated BSON types to Extended JSON (from all_types_json.js).
289+ func TestBsondumpAllTypesJSON (t * testing.T ) {
290+ testtype .SkipUnlessTestType (t , testtype .UnitTestType )
291+
292+ oid , err := bson .ObjectIDFromHex ("507f1f77bcf86cd799439011" )
293+ require .NoError (t , err )
294+ dec , err := bson .ParseDecimal128 ("1.2E+10" )
295+ require .NoError (t , err )
296+
297+ doc := bson.D {
298+ {"double" , 2.0 },
299+ {"string" , "hi" },
300+ {"doc" , bson.D {{"x" , int32 (1 )}}},
301+ {"arr" , bson.A {int32 (1 ), int32 (2 )}},
302+ {"binary" , bson.Binary {Subtype : 0x00 , Data : []byte {0xff , 0xff }}},
303+ {"oid" , oid },
304+ {"bool" , true },
305+ {"date" , bson .DateTime (978312200000 )},
306+ {"code" , bson.CodeWithScope {Code : "hi" , Scope : bson.D {{"x" , int32 (1 )}}}},
307+ {"ts" , bson.Timestamp {T : 1 , I : 2 }},
308+ {"int32" , int32 (5 )},
309+ {"int64" , int64 (6 )},
310+ {"dec" , dec },
311+ {"minkey" , bson.MinKey {}},
312+ {"maxkey" , bson.MaxKey {}},
313+ {"regex" , bson.Regex {Pattern : "^abc" , Options : "imx" }},
314+ {"symbol" , bson .Symbol ("i am a symbol" )},
315+ {"undefined" , bson.Undefined {}},
316+ {"dbpointer" , bson.DBPointer {DB : "some.namespace" , Pointer : oid }},
317+ {"null" , bson.Null {}},
318+ }
319+
320+ bsonData , err := bson .Marshal (doc )
321+ require .NoError (t , err )
322+ tmpFile , err := os .CreateTemp (t .TempDir (), "all_types_*.bson" )
323+ require .NoError (t , err )
324+ _ , err = tmpFile .Write (bsonData )
325+ require .NoError (t , err )
326+ require .NoError (t , tmpFile .Close ())
327+
328+ expectedJSON , err := bson .MarshalExtJSON (doc , true , false )
329+ require .NoError (t , err )
330+
331+ out , err := runBsondump ("--type=json" , tmpFile .Name ())
332+ require .NoError (t , err , "bsondump should exit successfully with 0" )
333+
334+ assert .Contains (
335+ t ,
336+ out ,
337+ "1 objects found" ,
338+ "should print out all top-level documents from the test data" ,
339+ )
340+
341+ var jsonLine string
342+ for line := range strings .Lines (out ) {
343+ if strings .Contains (line , "$oid" ) {
344+ jsonLine = line
345+ break
346+ }
347+ }
348+ require .NotEmpty (t , jsonLine , "should find a JSON output line containing Extended JSON" )
349+ assert .JSONEq (t , string (expectedJSON ), jsonLine )
350+ }
351+
352+ // TestBsondumpDeepNested verifies bsondump handles deeply nested BSON
353+ // documents without error in both JSON and debug modes (from deep_nested.js).
354+ func TestBsondumpDeepNested (t * testing.T ) {
355+ testtype .SkipUnlessTestType (t , testtype .UnitTestType )
356+
357+ _ , err := runBsondump ("--type=json" , "testdata/deep_nested.bson" )
358+ assert .NoError (t , err , "bsondump should handle deeply nested documents in JSON mode" )
359+
360+ _ , err = runBsondump ("--type=debug" , "testdata/deep_nested.bson" )
361+ assert .NoError (t , err , "bsondump should handle deeply nested documents in debug mode" )
362+ }
363+
364+ // TestBsondumpBadFiles verifies bsondump error handling for malformed BSON
365+ // input with and without --objcheck (from bad_files.js).
366+ func TestBsondumpBadFiles (t * testing.T ) {
367+ testtype .SkipUnlessTestType (t , testtype .UnitTestType )
368+
369+ badFiles := []string {
370+ "testdata/bad_cstring.bson" ,
371+ "testdata/bad_type.bson" ,
372+ "testdata/invalid_field_name.bson" ,
373+ "testdata/partial_file.bson" ,
374+ "testdata/random_bytes.bson" ,
375+ }
376+ for _ , f := range badFiles {
377+ _ , err := runBsondump ("--objcheck" , f )
378+ assert .Error (t , err , "--objcheck %s should exit with error" , f )
379+
380+ _ , err = runBsondump ("--objcheck" , "--type=debug" , f )
381+ assert .Error (t , err , "--objcheck --type=debug %s should exit with error" , f )
382+ }
383+
384+ _ , err := runBsondump ("--objcheck" , "testdata/broken_array.bson" )
385+ assert .NoError (t , err , "--objcheck broken_array.bson should succeed" )
386+
387+ _ , err = runBsondump ("--objcheck" , "--type=debug" , "testdata/broken_array.bson" )
388+ assert .NoError (t , err , "--objcheck --type=debug broken_array.bson should succeed" )
389+
390+ out , err := runBsondump ("testdata/bad_cstring.bson" )
391+ assert .NoError (t , err , "bad_cstring.bson without --objcheck should not error" )
392+ assert .Contains (
393+ t ,
394+ out ,
395+ "unable to dump document" ,
396+ "bad_cstring.bson should report a corrupted document in output" ,
397+ )
398+ }
399+
400+ // TestBsondumpOptionValidation verifies bsondump accepts valid options and
401+ // rejects invalid ones (from bsondump_options.js).
402+ func TestBsondumpOptionValidation (t * testing.T ) {
403+ testtype .SkipUnlessTestType (t , testtype .UnitTestType )
404+
405+ t .Run ("invalid --type fails" , func (t * testing.T ) {
406+ _ , err := runBsondump ("--type=fake" , "testdata/sample.bson" )
407+ assert .Error (t , err )
408+ })
409+ t .Run ("nonexistent file fails" , func (t * testing.T ) {
410+ _ , err := runBsondump ("testdata/does_not_exist.bson" )
411+ assert .Error (t , err )
412+ })
413+ t .Run ("--noobjcheck fails" , func (t * testing.T ) {
414+ _ , err := runBsondump ("--noobjcheck" , "testdata/sample.bson" )
415+ assert .Error (t , err )
416+ })
417+ t .Run ("--collection fails" , func (t * testing.T ) {
418+ _ , err := runBsondump ("--collection" , "testdata/sample.bson" )
419+ assert .Error (t , err )
420+ })
421+ t .Run ("multiple positional args fails" , func (t * testing.T ) {
422+ _ , err := runBsondump ("testdata/sample.bson" , "testdata/sample.bson" )
423+ assert .Error (t , err )
424+ })
425+ t .Run ("--bsonFile with extra positional arg fails" , func (t * testing.T ) {
426+ _ , err := runBsondump ("--bsonFile" , "testdata/sample.bson" , "testdata/sample.bson" )
427+ assert .Error (t , err )
428+ })
429+ t .Run ("-vvvv succeeds" , func (t * testing.T ) {
430+ _ , err := runBsondump ("-vvvv" , "testdata/sample.bson" )
431+ assert .NoError (t , err )
432+ })
433+ t .Run ("--verbose succeeds" , func (t * testing.T ) {
434+ _ , err := runBsondump ("--verbose" , "testdata/sample.bson" )
435+ assert .NoError (t , err )
436+ })
437+ t .Run ("--quiet suppresses status but still outputs data" , func (t * testing.T ) {
438+ out , err := runBsondump ("--quiet" , "testdata/sample.bson" )
439+ assert .NoError (t , err )
440+ assert .Contains (t , out , "I am a string" ,
441+ "JSON content should still be output with --quiet" )
442+ assert .NotContains (t , out , "objects found" ,
443+ "status line should be suppressed by --quiet" )
444+ })
445+ t .Run ("--help succeeds and prints usage" , func (t * testing.T ) {
446+ out , err := runBsondump ("--help" )
447+ assert .NoError (t , err )
448+ assert .Contains (t , out , "Usage" )
449+ })
450+ t .Run ("--version succeeds and prints version" , func (t * testing.T ) {
451+ out , err := runBsondump ("--version" )
452+ assert .NoError (t , err )
453+ assert .Contains (t , out , "version" )
454+ })
455+ }
456+
263457func bsondumpCommand (args ... string ) * exec.Cmd {
264458 cmd := []string {"go" , "run" , filepath .Join (".." , "bsondump" , "main" )}
265459 cmd = append (cmd , args ... )
0 commit comments