@@ -3016,3 +3016,144 @@ func writeFieldFile(t *testing.T, dir, name string, fields []string) string {
30163016 require .NoError (t , os .WriteFile (path , []byte (strings .Join (fields , "\n " )+ "\n " ), 0o644 ))
30173017 return path
30183018}
3019+
3020+ // TestImportDocumentValidation verifies mongoimport behavior with collection
3021+ // validators: normal import skips invalid docs, --bypassDocumentValidation
3022+ // imports all, --stopOnError and --maintainInsertionOrder fail on validation
3023+ // errors.
3024+ func TestImportDocumentValidation (t * testing.T ) {
3025+ testtype .SkipUnlessTestType (t , testtype .IntegrationTestType )
3026+
3027+ const dbName = "mongoimport_docvalidation_test"
3028+ const collName = "bar"
3029+
3030+ sessionProvider , _ , err := testutil .GetBareSessionProvider ()
3031+ require .NoError (t , err )
3032+ client , err := sessionProvider .GetSession ()
3033+ require .NoError (t , err )
3034+ t .Cleanup (func () {
3035+ if err := client .Database (dbName ).Drop (context .Background ()); err != nil {
3036+ t .Errorf ("dropping test database: %v" , err )
3037+ }
3038+ })
3039+
3040+ db := client .Database (dbName )
3041+
3042+ // 1000 docs: even-indexed lack `baz` (invalid), odd-indexed have `baz` (valid).
3043+ docs := make ([]any , 1000 )
3044+ for i := range 1000 {
3045+ if i % 2 == 0 {
3046+ docs [i ] = bson.D {{"_id" , int32 (i )}, {"num" , int32 (i + 1 )}, {"s" , fmt .Sprintf ("%d" , i )}}
3047+ } else {
3048+ docs [i ] = bson.D {{"_id" , int32 (i )}, {"num" , int32 (i + 1 )}, {"s" , fmt .Sprintf ("%d" , i )}, {"baz" , int32 (i )}}
3049+ }
3050+ }
3051+ _ , err = db .Collection (collName ).InsertMany (t .Context (), docs )
3052+ require .NoError (t , err )
3053+
3054+ exportToolOptions , err := testutil .GetToolOptions ()
3055+ require .NoError (t , err )
3056+ exportToolOptions .Namespace = & options.Namespace {DB : dbName , Collection : collName }
3057+ me , err := mongoexport .New (mongoexport.Options {
3058+ ToolOptions : exportToolOptions ,
3059+ OutputFormatOptions : & mongoexport.OutputFormatOptions {
3060+ Type : "json" ,
3061+ JSONFormat : "canonical" ,
3062+ },
3063+ InputOptions : & mongoexport.InputOptions {},
3064+ })
3065+ require .NoError (t , err )
3066+ defer me .Close ()
3067+ tmpFile , err := os .CreateTemp (t .TempDir (), "export-*.json" )
3068+ require .NoError (t , err )
3069+ _ , err = me .Export (tmpFile )
3070+ require .NoError (t , err )
3071+ require .NoError (t , tmpFile .Close ())
3072+
3073+ validator := bson.D {{"baz" , bson.D {{"$exists" , true }}}}
3074+ ns := & options.Namespace {DB : dbName , Collection : collName }
3075+
3076+ t .Run ("no validator imports all docs" , func (t * testing.T ) {
3077+ require .NoError (t , db .Drop (t .Context ()))
3078+ require .NoError (t , importWithIngestOpts (t , ns , tmpFile .Name (), IngestOptions {}))
3079+ n , err := db .Collection (collName ).CountDocuments (t .Context (), bson.D {})
3080+ require .NoError (t , err )
3081+ assert .EqualValues (t , 1000 , n , "import without validation should import all 1000 documents" )
3082+ })
3083+
3084+ t .Run ("validator skips invalid docs" , func (t * testing.T ) {
3085+ recreateWithValidator (t , db , validator )
3086+ require .NoError (t , importWithIngestOpts (t , ns , tmpFile .Name (), IngestOptions {}))
3087+ n , err := db .Collection (collName ).CountDocuments (t .Context (), bson.D {})
3088+ require .NoError (t , err )
3089+ assert .EqualValues (t , 500 , n , "only valid documents should be imported" )
3090+ })
3091+
3092+ t .Run ("stopOnError fails on validation errors" , func (t * testing.T ) {
3093+ recreateWithValidator (t , db , validator )
3094+ err := importWithIngestOpts (t , ns , tmpFile .Name (), IngestOptions {StopOnError : true })
3095+ assertValidationError (t , err , "import with --stopOnError should fail on validation errors" )
3096+ })
3097+
3098+ t .Run ("maintainInsertionOrder fails on validation errors" , func (t * testing.T ) {
3099+ recreateWithValidator (t , db , validator )
3100+ err := importWithIngestOpts (
3101+ t , ns , tmpFile .Name (), IngestOptions {MaintainInsertionOrder : true },
3102+ )
3103+ assertValidationError (
3104+ t , err , "import with --maintainInsertionOrder should fail on validation errors" ,
3105+ )
3106+ })
3107+
3108+ t .Run ("bypassDocumentValidation imports all docs" , func (t * testing.T ) {
3109+ recreateWithValidator (t , db , validator )
3110+ require .NoError (t , importWithIngestOpts (
3111+ t , ns , tmpFile .Name (), IngestOptions {BypassDocumentValidation : true },
3112+ ))
3113+ n , err := db .Collection (collName ).CountDocuments (t .Context (), bson.D {})
3114+ require .NoError (t , err )
3115+ assert .EqualValues (
3116+ t , 1000 , n , "all documents should be imported with --bypassDocumentValidation" ,
3117+ )
3118+ })
3119+ }
3120+
3121+ func recreateWithValidator (t * testing.T , db * mongo.Database , validator any ) {
3122+ t .Helper ()
3123+ require .NoError (t , db .Drop (context .Background ()))
3124+ require .NoError (
3125+ t ,
3126+ db .CreateCollection (
3127+ context .Background (), "bar" , mopt .CreateCollection ().SetValidator (validator ),
3128+ ),
3129+ )
3130+ }
3131+
3132+ func assertValidationError (t * testing.T , err error , msg string ) {
3133+ t .Helper ()
3134+ var bwe mongo.BulkWriteException
3135+ if assert .ErrorAs (t , err , & bwe , msg ) {
3136+ assert .NotEmpty (t , bwe .WriteErrors , "should have at least one write error" )
3137+ assert .Equal (t , 121 , bwe .WriteErrors [0 ].Code , "should be DocumentValidationFailure (121)" )
3138+ }
3139+ }
3140+
3141+ func importWithIngestOpts (
3142+ t * testing.T ,
3143+ ns * options.Namespace ,
3144+ filePath string ,
3145+ ingestOpts IngestOptions ,
3146+ ) error {
3147+ t .Helper ()
3148+ toolOptions , err := testutil .GetToolOptions ()
3149+ require .NoError (t , err )
3150+ toolOptions .Namespace = ns
3151+ mi , err := New (Options {
3152+ ToolOptions : toolOptions ,
3153+ InputOptions : & InputOptions {File : filePath , ParseGrace : "stop" },
3154+ IngestOptions : & ingestOpts ,
3155+ })
3156+ require .NoError (t , err )
3157+ _ , _ , err = mi .ImportDocuments ()
3158+ return err
3159+ }
0 commit comments