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