1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
# Matcher for determining if the server is of the expected type according to
# the test.
#
# @since 2.0.0
RSpec::Matchers.define :be_server_type do |expected|
match do |actual|
case expected
when 'Standalone' then actual.standalone?
when 'RSPrimary' then actual.primary?
when 'RSSecondary' then actual.secondary?
when 'RSArbiter' then actual.arbiter?
when 'Mongos' then actual.mongos?
when 'Unknown' then actual.unknown?
when 'PossiblePrimary' then actual.unknown?
when 'RSGhost' then actual.ghost?
when 'RSOther' then actual.other?
end
end
end
# Matcher for determining if the cluster topology is the expected type.
#
# @since 2.0.0
RSpec::Matchers.define :be_topology do |expected|
match do |actual|
case expected
when 'ReplicaSetWithPrimary' then actual.replica_set?
when 'ReplicaSetNoPrimary' then actual.replica_set?
when 'Sharded' then actual.sharded?
when 'Single' then actual.single?
when 'Unknown' then actual.unknown?
end
end
end
module Mongo
module SDAM
# Convenience helper to find a server by it's URI.
#
# @since 2.0.0
def find_server(client, uri)
client.cluster.instance_variable_get(:@servers).detect{ |s| s.address.to_s == uri }
end
# Helper to convert an extended JSON ObjectId electionId to BSON::ObjectId.
#
# @since 2.1.0
def self.convert_election_ids(docs)
docs.each do |doc |
doc['electionId'] = BSON::ObjectId.from_string(doc['electionId']['$oid']) if doc['electionId']
end
end
# Represents a specification.
#
# @since 2.0.0
class Spec
# @return [ String ] description The spec description.
attr_reader :description
# @return [ Array<Phase> ] phases The spec phases.
attr_reader :phases
# @return [ Mongo::URI ] uri The URI object.
attr_reader :uri
# @return [ String ] uri_string The passed uri string.
attr_reader :uri_string
# Instantiate the new spec.
#
# @example Create the spec.
# Spec.new(file)
#
# @param [ String ] file The name of the file.
#
# @since 2.0.0
def initialize(file)
file = File.new(file)
@test = YAML.load(ERB.new(file.read).result)
file.close
@description = @test['description']
@uri_string = @test['uri']
@uri = URI.new(uri_string)
@phases = @test['phases'].map{ |phase| Phase.new(phase, uri) }
end
end
# Represents a phase in the spec. Phases are sequential.
#
# @since 2.0.0
class Phase
# @return [ Outcome ] outcome The phase outcome.
attr_reader :outcome
# @return [ Array<Response> ] responses The responses for each server in
# the phase.
attr_reader :responses
# Create the new phase.
#
# @example Create the new phase.
# Phase.new(phase, uri)
#
# @param [ Hash ] phase The phase hash.
# @param [ Mongo::URI ] uri The URI.
#
# @since 2.0.0
def initialize(phase, uri)
@phase = phase
@responses = @phase['responses'].map{ |response| Response.new(SDAM::convert_election_ids(response), uri) }
@outcome = Outcome.new(@phase['outcome'])
end
end
# Represents a server response during a phase.
#
# @since 2.0.0
class Response
# @return [ String ] address The server address.
attr_reader :address
# @return [ Hash ] ismaster The ismaster response.
attr_reader :ismaster
# Create the new response.
#
# @example Create the response.
# Response.new(response, uri)
#
# @param [ Hash ] response The response value.
# @param [ Mongo::URI ] uri The URI.
#
# @since 2.0.0
def initialize(response, uri)
@uri = uri
@address = response[0]
@ismaster = response[1]
end
end
# Get the outcome or expectations from the phase.
#
# @since 2.0.0
class Outcome
# @return [ Array ] events The expected events.
attr_reader :events
# @return [ Hash ] servers The expecations for
# server states.
attr_reader :servers
# @return [ String ] set_name The expected RS set name.
attr_reader :set_name
# @return [ String ] topology_type The expected cluster topology type.
attr_reader :topology_type
# @return [ Integer, nil ] logical_session_timeout The expected logical session timeout.
attr_reader :logical_session_timeout
# Create the new outcome.
#
# @example Create the new outcome.
# Outcome.new(outcome)
#
# @param [ Hash ] outcome The outcome object.
#
# @since 2.0.0
def initialize(outcome)
@servers = process_servers(outcome['servers']) if outcome['servers']
@set_name = outcome['setName']
@topology_type = outcome['topologyType']
@logical_session_timeout = outcome['logicalSessionTimeoutMinutes']
@events = process_events(outcome['events']) if outcome['events']
@compatible = outcome['compatible']
end
# Whether the server responses indicate that their versions are supported by the driver.
#
# @example Do the server responses indicate that their versions are supported by the driver.
# outcome.compatible?
#
# @return [ true, false ] Whether the server versions are compatible with the driver.
#
# @since 2.5.1
def compatible?
@compatible.nil? || !!@compatible
end
private
def process_events(events)
events.map do |event|
Event.new(event.keys.first, event.values.first)
end
end
def process_servers(servers)
servers.each do |s|
SDAM::convert_election_ids([ s[1] ])
end
end
end
class Event
MAPPINGS = {
'server_closed_event' => Mongo::Monitoring::Event::ServerClosed,
'server_description_changed_event' => Mongo::Monitoring::Event::ServerDescriptionChanged,
'server_opening_event' => Mongo::Monitoring::Event::ServerOpening,
'topology_description_changed_event' => Mongo::Monitoring::Event::TopologyChanged,
'topology_opening_event' => Mongo::Monitoring::Event::TopologyOpening
}
attr_reader :name
attr_reader :data
def initialize(name, data)
@name = name
@data = data
end
def expected
MAPPINGS.fetch(name)
end
end
end
end
|