1// Copyright (C) MongoDB, Inc. 2017-present. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 6 7package integration 8 9import ( 10 "io/ioutil" 11 "path" 12 "reflect" 13 "strings" 14 "testing" 15 16 "go.mongodb.org/mongo-driver/bson" 17 "go.mongodb.org/mongo-driver/internal/testutil/assert" 18 "go.mongodb.org/mongo-driver/mongo" 19 "go.mongodb.org/mongo-driver/mongo/integration/mtest" 20) 21 22const ( 23 crudTestsDir = "../../data/crud" 24 crudReadDir = "v1/read" 25 crudWriteDir = "v1/write" 26) 27 28type crudTestFile struct { 29 Data []bson.Raw `bson:"data"` 30 MinServerVersion string `bson:"minServerVersion"` 31 MaxServerVersion string `bson:"maxServerVersion"` 32 Tests []crudTest `bson:"tests"` 33} 34 35type crudTest struct { 36 Description string `bson:"description"` 37 SkipReason string `bson:"skipReason"` 38 Operation crudOperation `bson:"operation"` 39 Outcome crudOutcome `bson:"outcome"` 40} 41 42type crudOperation struct { 43 Name string `bson:"name"` 44 Arguments bson.Raw `bson:"arguments"` 45} 46 47type crudOutcome struct { 48 Error bool `bson:"error"` // only used by retryable writes tests 49 Result interface{} `bson:"result"` 50 Collection *outcomeCollection `bson:"collection"` 51} 52 53var crudRegistry = bson.NewRegistryBuilder(). 54 RegisterTypeMapEntry(bson.TypeEmbeddedDocument, reflect.TypeOf(bson.Raw{})).Build() 55 56func TestCrudSpec(t *testing.T) { 57 for _, dir := range []string{crudReadDir, crudWriteDir} { 58 for _, file := range jsonFilesInDir(t, path.Join(crudTestsDir, dir)) { 59 t.Run(file, func(t *testing.T) { 60 runCrudFile(t, path.Join(crudTestsDir, dir, file)) 61 }) 62 } 63 } 64} 65 66func runCrudFile(t *testing.T, file string) { 67 content, err := ioutil.ReadFile(file) 68 assert.Nil(t, err, "ReadFile error for %v: %v", file, err) 69 70 var testFile crudTestFile 71 err = bson.UnmarshalExtJSONWithRegistry(crudRegistry, content, false, &testFile) 72 assert.Nil(t, err, "UnmarshalExtJSONWithRegistry error: %v", err) 73 74 mt := mtest.New(t, mtest.NewOptions().MinServerVersion(testFile.MinServerVersion).MaxServerVersion(testFile.MaxServerVersion)) 75 defer mt.Close() 76 77 for _, test := range testFile.Tests { 78 mt.Run(test.Description, func(mt *mtest.T) { 79 runCrudTest(mt, test, testFile) 80 }) 81 } 82} 83 84func runCrudTest(mt *mtest.T, test crudTest, testFile crudTestFile) { 85 if len(testFile.Data) > 0 { 86 docs := rawSliceToInterfaceSlice(testFile.Data) 87 _, err := mt.Coll.InsertMany(mtest.Background, docs) 88 assert.Nil(mt, err, "InsertMany error: %v", err) 89 } 90 91 runCrudOperation(mt, test.Description, test.Operation, test.Outcome) 92} 93 94func verifyCrudError(mt *mtest.T, outcome crudOutcome, err error) { 95 opError := errorFromResult(mt, outcome.Result) 96 if opError == nil { 97 return 98 } 99 verificationErr := verifyError(opError, err) 100 assert.Nil(mt, verificationErr, "error mismatch: %v", verificationErr) 101} 102 103// run a CRUD operation and verify errors and outcomes. 104// the test description is needed to see determine if the test is an aggregate with $out 105func runCrudOperation(mt *mtest.T, testDescription string, operation crudOperation, outcome crudOutcome) { 106 switch operation.Name { 107 case "aggregate": 108 cursor, err := executeAggregate(mt, mt.Coll, nil, operation.Arguments) 109 if outcome.Error { 110 assert.NotNil(mt, err, "expected Aggregate error, got nil") 111 verifyCrudError(mt, outcome, err) 112 break 113 } 114 assert.Nil(mt, err, "Aggregate error: %v", err) 115 // only verify cursor contents for pipelines without $out 116 if !strings.Contains(testDescription, "$out") { 117 verifyCursorResult(mt, cursor, outcome.Result) 118 } 119 case "bulkWrite": 120 res, err := executeBulkWrite(mt, nil, operation.Arguments) 121 if outcome.Error { 122 assert.NotNil(mt, err, "expected BulkWrite error, got nil") 123 verifyCrudError(mt, outcome, err) 124 break 125 } 126 assert.Nil(mt, err, "BulkWrite error: %v", err) 127 verifyBulkWriteResult(mt, res, outcome.Result) 128 case "count": 129 res, err := executeCountDocuments(mt, nil, operation.Arguments) 130 if outcome.Error { 131 assert.NotNil(mt, err, "expected CountDocuments error, got nil") 132 verifyCrudError(mt, outcome, err) 133 break 134 } 135 assert.Nil(mt, err, "CountDocuments error: %v", err) 136 verifyCountResult(mt, res, outcome.Result) 137 case "distinct": 138 res, err := executeDistinct(mt, nil, operation.Arguments) 139 if outcome.Error { 140 assert.NotNil(mt, err, "expected Distinct error, got nil") 141 verifyCrudError(mt, outcome, err) 142 break 143 } 144 assert.Nil(mt, err, "Distinct error: %v", err) 145 verifyDistinctResult(mt, res, outcome.Result) 146 case "find": 147 cursor, err := executeFind(mt, nil, operation.Arguments) 148 if outcome.Error { 149 assert.NotNil(mt, err, "expected Find error, got nil") 150 verifyCrudError(mt, outcome, err) 151 break 152 } 153 assert.Nil(mt, err, "Find error: %v", err) 154 verifyCursorResult(mt, cursor, outcome.Result) 155 case "deleteOne": 156 res, err := executeDeleteOne(mt, nil, operation.Arguments) 157 if outcome.Error { 158 assert.NotNil(mt, err, "expected DeleteOne error, got nil") 159 verifyCrudError(mt, outcome, err) 160 break 161 } 162 assert.Nil(mt, err, "DeleteOne error: %v", err) 163 verifyDeleteResult(mt, res, outcome.Result) 164 case "deleteMany": 165 res, err := executeDeleteMany(mt, nil, operation.Arguments) 166 if outcome.Error { 167 assert.NotNil(mt, err, "expected DeleteMany error, got nil") 168 verifyCrudError(mt, outcome, err) 169 break 170 } 171 assert.Nil(mt, err, "DeleteMany error: %v", err) 172 verifyDeleteResult(mt, res, outcome.Result) 173 case "findOneAndDelete": 174 res := executeFindOneAndDelete(mt, nil, operation.Arguments) 175 err := res.Err() 176 if outcome.Error { 177 assert.NotNil(mt, err, "expected FindOneAndDelete error, got nil") 178 verifyCrudError(mt, outcome, err) 179 break 180 } 181 if outcome.Result == nil { 182 assert.Equal(mt, mongo.ErrNoDocuments, err, "expected error %v, got %v", mongo.ErrNoDocuments, err) 183 break 184 } 185 assert.Nil(mt, err, "FindOneAndDelete error: %v", err) 186 verifySingleResult(mt, res, outcome.Result) 187 case "findOneAndReplace": 188 res := executeFindOneAndReplace(mt, nil, operation.Arguments) 189 err := res.Err() 190 if outcome.Error { 191 assert.NotNil(mt, err, "expected FindOneAndReplace error, got nil") 192 verifyCrudError(mt, outcome, err) 193 break 194 } 195 if outcome.Result == nil { 196 assert.Equal(mt, mongo.ErrNoDocuments, err, "expected error %v, got %v", mongo.ErrNoDocuments, err) 197 break 198 } 199 assert.Nil(mt, err, "FindOneAndReplace error: %v", err) 200 verifySingleResult(mt, res, outcome.Result) 201 case "findOneAndUpdate": 202 res := executeFindOneAndUpdate(mt, nil, operation.Arguments) 203 err := res.Err() 204 if outcome.Error { 205 assert.NotNil(mt, err, "expected FindOneAndUpdate error, got nil") 206 verifyCrudError(mt, outcome, err) 207 break 208 } 209 if outcome.Result == nil { 210 assert.Equal(mt, mongo.ErrNoDocuments, err, "expected error %v, got %v", mongo.ErrNoDocuments, err) 211 break 212 } 213 assert.Nil(mt, err, "FindOneAndUpdate error: %v", err) 214 verifySingleResult(mt, res, outcome.Result) 215 case "insertOne": 216 res, err := executeInsertOne(mt, nil, operation.Arguments) 217 if outcome.Error { 218 assert.NotNil(mt, err, "expected InsertOne error, got nil") 219 verifyCrudError(mt, outcome, err) 220 break 221 } 222 assert.Nil(mt, err, "InsertOne error: %v", err) 223 verifyInsertOneResult(mt, res, outcome.Result) 224 case "insertMany": 225 res, err := executeInsertMany(mt, nil, operation.Arguments) 226 if outcome.Error { 227 assert.NotNil(mt, err, "expected InsertMany error, got nil") 228 verifyCrudError(mt, outcome, err) 229 break 230 } 231 assert.Nil(mt, err, "InsertMany error: %v", err) 232 verifyInsertManyResult(mt, res, outcome.Result) 233 case "replaceOne": 234 res, err := executeReplaceOne(mt, nil, operation.Arguments) 235 if outcome.Error { 236 assert.NotNil(mt, err, "expected ReplaceOne error, got nil") 237 verifyCrudError(mt, outcome, err) 238 break 239 } 240 assert.Nil(mt, err, "ReplaceOne error: %v", err) 241 verifyUpdateResult(mt, res, outcome.Result) 242 case "updateOne": 243 res, err := executeUpdateOne(mt, nil, operation.Arguments) 244 if outcome.Error { 245 assert.NotNil(mt, err, "expected UpdateOne error, got nil") 246 verifyCrudError(mt, outcome, err) 247 break 248 } 249 assert.Nil(mt, err, "UpdateOne error: %v", err) 250 verifyUpdateResult(mt, res, outcome.Result) 251 case "updateMany": 252 res, err := executeUpdateMany(mt, nil, operation.Arguments) 253 if outcome.Error { 254 assert.NotNil(mt, err, "expected UpdateMany error, got nil") 255 verifyCrudError(mt, outcome, err) 256 break 257 } 258 assert.Nil(mt, err, "UpdateMany error: %v", err) 259 verifyUpdateResult(mt, res, outcome.Result) 260 case "estimatedDocumentCount": 261 res, err := executeEstimatedDocumentCount(mt, nil, operation.Arguments) 262 if outcome.Error { 263 assert.NotNil(mt, err, "expected EstimatedDocumentCount error, got nil") 264 verifyCrudError(mt, outcome, err) 265 break 266 } 267 assert.Nil(mt, err, "EstimatedDocumentCount error: %v", err) 268 verifyCountResult(mt, res, outcome.Result) 269 case "countDocuments": 270 res, err := executeCountDocuments(mt, nil, operation.Arguments) 271 if outcome.Error { 272 assert.NotNil(mt, err, "expected CountDocuments error, got nil") 273 verifyCrudError(mt, outcome, err) 274 break 275 } 276 assert.Nil(mt, err, "CountDocuments error: %v", err) 277 verifyCountResult(mt, res, outcome.Result) 278 default: 279 mt.Fatalf("unrecognized operation: %v", operation.Name) 280 } 281 282 if outcome.Collection != nil { 283 verifyTestOutcome(mt, outcome.Collection) 284 } 285} 286