// Copyright (C) MongoDB, Inc. 2017-present. // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 package drivertest import ( "context" "errors" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/csot" "go.mongodb.org/mongo-driver/v2/internal/driverutil" "go.mongodb.org/mongo-driver/v2/mongo/address" "go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore" "go.mongodb.org/mongo-driver/v2/x/mongo/driver" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/description" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mnet" "go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage" ) const ( serverAddress = address.Address("127.0.0.1:27017") maxDocumentSize uint32 = 16777216 maxMessageSize uint32 = 48000000 maxBatchCount uint32 = 100000 ) var ( sessionTimeoutMinutes int64 = 30 // MockDescription is the server description used for the mock deployment. Each mocked connection returns this // value from its Description method. MockDescription = description.Server{ CanonicalAddr: serverAddress, MaxDocumentSize: maxDocumentSize, MaxMessageSize: maxMessageSize, MaxBatchCount: maxBatchCount, SessionTimeoutMinutes: &sessionTimeoutMinutes, Kind: description.ServerKindRSPrimary, WireVersion: &description.VersionRange{ Max: driverutil.MaxWireVersion, }, } // ErrNoResponsesRemaining is the error returned when the mocked connection is asked for a response and does not have // any responses buffered. ErrNoResponsesRemaining = errors.New("no responses remaining") ) // connection implements the driver.Connection interface and responds to wire messages with pre-configured responses. type connection struct { responses []bson.D // responses to send when ReadWireMessage is called } var ( _ mnet.ReadWriteCloser = &connection{} _ mnet.Describer = &connection{} ) // Write is a no-op. func (c *connection) Write(context.Context, []byte) error { return nil } func (c *connection) OIDCTokenGenID() uint64 { return 0 } func (c *connection) SetOIDCTokenGenID(uint64) { } // Read returns the next response in the connection's list of responses. func (c *connection) Read(_ context.Context) ([]byte, error) { var dst []byte if len(c.responses) == 0 { return dst, ErrNoResponsesRemaining } nextRes := c.responses[0] c.responses = c.responses[1:] var wmindex int32 wmindex, dst = wiremessage.AppendHeaderStart(dst, wiremessage.NextRequestID(), 0, wiremessage.OpMsg) dst = wiremessage.AppendMsgFlags(dst, 0) dst = wiremessage.AppendMsgSectionType(dst, wiremessage.SingleDocument) resBytes, _ := bson.Marshal(nextRes) dst = append(dst, resBytes...) dst = bsoncore.UpdateLength(dst, wmindex, int32(len(dst[wmindex:]))) return dst, nil } // Description returns a fixed server description for the connection. func (c *connection) Description() description.Server { return MockDescription } // Close is a no-op operation. func (*connection) Close() error { return nil } // ID returns a fixed identifier for the connection. func (*connection) ID() string { return "" } // DriverConnectionID returns a fixed identifier for the driver pool connection. func (*connection) DriverConnectionID() int64 { return 0 } // ServerConnectionID returns a fixed identifier for the server connection. func (*connection) ServerConnectionID() *int64 { serverConnectionID := int64(42) return &serverConnectionID } // Address returns a fixed address for the connection. func (*connection) Address() address.Address { return serverAddress } // Stale returns if the connection is stale. func (*connection) Stale() bool { return false } // MockDeployment wraps a connection and implements the driver.Deployment interface. type MockDeployment struct { conn *connection updates chan description.Topology } var ( _ driver.Deployment = &MockDeployment{} _ driver.Server = &MockDeployment{} _ driver.Connector = &MockDeployment{} _ driver.Disconnector = &MockDeployment{} _ driver.Subscriber = &MockDeployment{} ) // SelectServer implements the Deployment interface. This method does not use the // description.SelectedServer provided and instead returns itself. The Connections returned from the // Connection method have a no-op Close method. func (md *MockDeployment) SelectServer(context.Context, description.ServerSelector) (driver.Server, error) { return md, nil } // GetServerSelectionTimeout returns zero as a server selection timeout is not // applicable for mock deployments. func (*MockDeployment) GetServerSelectionTimeout() time.Duration { return 0 } // Kind implements the Deployment interface. It always returns description.TopologyKindSingle. func (md *MockDeployment) Kind() description.TopologyKind { return description.TopologyKindSingle } // Connection implements the driver.Server interface. func (md *MockDeployment) Connection(context.Context) (*mnet.Connection, error) { return mnet.NewConnection(md.conn), nil } // RTTMonitor implements the driver.Server interface. func (md *MockDeployment) RTTMonitor() driver.RTTMonitor { return &csot.ZeroRTTMonitor{} } // Connect is a no-op method which implements the driver.Connector interface. func (md *MockDeployment) Connect() error { return nil } // Disconnect is a no-op method which implements the driver.Disconnector interface { func (md *MockDeployment) Disconnect(context.Context) error { close(md.updates) return nil } // Subscribe returns a subscription from which new topology descriptions can be retrieved. // Subscribe implements the driver.Subscriber interface. func (md *MockDeployment) Subscribe() (*driver.Subscription, error) { if md.updates == nil { md.updates = make(chan description.Topology, 1) md.updates <- description.Topology{ SessionTimeoutMinutes: &sessionTimeoutMinutes, } } return &driver.Subscription{ Updates: md.updates, }, nil } // Unsubscribe is a no-op method which implements the driver.Subscriber interface. func (md *MockDeployment) Unsubscribe(*driver.Subscription) error { return nil } // AddResponses adds responses to this mock deployment. func (md *MockDeployment) AddResponses(responses ...bson.D) { md.conn.responses = append(md.conn.responses, responses...) } // ClearResponses clears all remaining responses in this mock deployment. func (md *MockDeployment) ClearResponses() { md.conn.responses = md.conn.responses[:0] } // NewMockDeployment returns a mock driver.Deployment that responds with OP_MSG wire messages. // However, for most use cases, we suggest testing with an actual database. func NewMockDeployment(responses ...bson.D) *MockDeployment { return &MockDeployment{ conn: &connection{ responses: responses, }, } }