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 unified
8
9import (
10	"bytes"
11	"context"
12	"fmt"
13
14	"go.mongodb.org/mongo-driver/bson"
15	testhelpers "go.mongodb.org/mongo-driver/internal/testutil/helpers"
16	"go.mongodb.org/mongo-driver/mongo/integration/mtest"
17	"go.mongodb.org/mongo-driver/mongo/options"
18	"go.mongodb.org/mongo-driver/mongo/readconcern"
19	"go.mongodb.org/mongo-driver/mongo/readpref"
20)
21
22type CollectionData struct {
23	DatabaseName   string     `bson:"databaseName"`
24	CollectionName string     `bson:"collectionName"`
25	Documents      []bson.Raw `bson:"documents"`
26}
27
28// CreateCollection configures the collection represented by the receiver using the internal client. This function
29// first drops the collection and then creates it and inserts the seed data if needed.
30func (c *CollectionData) CreateCollection(ctx context.Context) error {
31	db := mtest.GlobalClient().Database(c.DatabaseName)
32	coll := db.Collection(c.CollectionName, options.Collection().SetWriteConcern(mtest.MajorityWc))
33	if err := coll.Drop(ctx); err != nil {
34		return fmt.Errorf("error dropping collection: %v", err)
35	}
36
37	// If no data is given, create the collection with write concern "majority".
38	if len(c.Documents) == 0 {
39		// The write concern has to be manually specified in the command document because RunCommand does not honor
40		// the database's write concern.
41		create := bson.D{
42			{"create", coll.Name()},
43			{"writeConcern", bson.D{
44				{"w", "majority"},
45			}},
46		}
47		if err := db.RunCommand(ctx, create).Err(); err != nil {
48			return fmt.Errorf("error creating collection: %v", err)
49		}
50		return nil
51	}
52
53	docs := testhelpers.RawSliceToInterfaceSlice(c.Documents)
54	if _, err := coll.InsertMany(ctx, docs); err != nil {
55		return fmt.Errorf("error inserting data: %v", err)
56	}
57	return nil
58}
59
60// VerifyContents asserts that the collection on the server represented by this CollectionData instance contains the
61// expected documents.
62func (c *CollectionData) VerifyContents(ctx context.Context) error {
63	collOpts := options.Collection().
64		SetReadPreference(readpref.Primary()).
65		SetReadConcern(readconcern.Local())
66	coll := mtest.GlobalClient().Database(c.DatabaseName).Collection(c.CollectionName, collOpts)
67
68	cursor, err := coll.Find(ctx, bson.D{}, options.Find().SetSort(bson.M{"_id": 1}))
69	if err != nil {
70		return fmt.Errorf("Find error: %v", err)
71	}
72	defer cursor.Close(ctx)
73
74	var docs []bson.Raw
75	if err := cursor.All(ctx, &docs); err != nil {
76		return fmt.Errorf("cursor iteration error: %v", err)
77	}
78
79	// Verify the slice lengths are equal. This also covers the case of asserting that the collection is empty if
80	// c.Documents is an empty slice.
81	if len(c.Documents) != len(docs) {
82		return fmt.Errorf("expected %d documents but found %d: %v", len(c.Documents), len(docs), docs)
83	}
84
85	// We can't use VerifyValuesMatch here because the rules for evaluating matches (e.g. flexible numeric comparisons
86	// and special $$ operators) do not apply when verifying collection outcomes. We have to permit variations in key
87	// order, though, so we sort documents before doing a byte-wise comparison.
88	for idx, expected := range c.Documents {
89		expected = SortDocument(expected)
90		actual := SortDocument(docs[idx])
91
92		if !bytes.Equal(expected, actual) {
93			return fmt.Errorf("document comparison error at index %d: expected %s, got %s", idx, expected, actual)
94		}
95	}
96	return nil
97}
98
99func (c *CollectionData) Namespace() string {
100	return fmt.Sprintf("%s.%s", c.DatabaseName, c.CollectionName)
101}
102