Class: RightDevelop::Testing::Recording::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/right_develop/testing/recording/config.rb

Overview

Config file format.

Defined Under Namespace

Classes: ConfigError

Constant Summary collapse

FIXTURES_DIR_NAME =

default relative directories.

'fixtures'.freeze
LOG_DIR_NAME =
'log'.freeze
STOP_TRAVERSAL_KEY =

the empty key is used as a stop traversal signal because any literal value would be ambiguous.

''.freeze
VALID_MODES =
::Mash.new(
  :echo     => 'Echoes request back as response and validates route.',
  :playback => 'Playback a session for one or more stubbed web services.',
  :record   => 'Record a session for one or more proxied web services.'
).freeze
ALLOWED_KINDS =

keys allowed under the deep route configuration.

%w(request response)
ALLOWED_CONFIG_ACTIONS =
%w(significant timeouts transform variables)
ALLOWED_TIMEOUTS =
%w(open_timeout read_timeout)
ALLOWED_VARIABLE_TYPES =
%w(body header query)
METADATA_CLASS =

metadata.

::RightDevelop::Testing::Recording::Metadata
FIXTURE_FILE_NAME_REGEX =

patterns for fixture files.

/^([0-9A-Fa-f]{32}).yml$/
TYPE_NAME_VALUE_REGEX =

typename to value expression for significant/requests/responses configurations.

/^(body|header|query|verb)(?:[:#]([^=]+))?=(.*)$/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_hash, options = nil) ⇒ Config

Returns a new instance of Config.



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
# File 'lib/right_develop/testing/recording/config.rb', line 69

def initialize(config_hash, options = nil)
  # defaults.
  current_dir = ::Dir.pwd
  defaults = ::Mash.new(
    'mode'         => :playback,
    'routes'       => {},
    'fixtures_dir' => ::File.expand_path(FIXTURES_DIR_NAME, current_dir),
    'log_level'    => :info,
    'log_dir'      => ::File.expand_path(LOG_DIR_NAME, current_dir),
    'throttle'     => 0,
  )
  unless config_hash.kind_of?(::Hash)
    raise ConfigError, 'config_hash must be a hash'
  end

  # shallow merge of hash because the defaults are a shallow hash. deep mash
  # of caller's config to deep clone and normalize keys.
  config_hash = defaults.merge(deep_mash(config_hash))
  if options
    # another deep merge of any additional options.
    ::RightSupport::Data::HashTools.deep_merge!(config_hash, options)
  end

  @config_hash = ::Mash.new
  mode(config_hash['mode'])
  routes(config_hash['routes'])
  log_dir(config_hash['log_dir'])
  log_level(config_hash['log_level'])
  fixtures_dir(config_hash['fixtures_dir'])
  throttle(config_hash['throttle'])
end

Class Method Details

.deep_mash(any) ⇒ Object

Deeply mashes and duplicates (clones) a hash containing other hashes or arrays of hashes but not other types.

Note that Mash.new(my_mash) will convert child hashes to mashes but not with the guarantee of cloning and detaching the deep mash. In other words. if any part of the hash is already a mash then it is not cloned by invoking Mash.new()

FIX: put this in HashTools ?

Returns:

  • (Object)

    depends on input type



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/right_develop/testing/recording/config.rb', line 238

def self.deep_mash(any)
  case any
  when Array
    # traverse arrays
    any.map { |i| deep_mash(i) }
  when Hash
    # mash the hash
    any.inject(::Mash.new) do |m, (k, v)|
      m[k] = deep_mash(v)
      m
    end
  else
    any  # whatever
  end
end

.from_file(path, options = nil) ⇒ Config

Loads the config from given path or a relative location.

Parameters:

  • path (String)

    to configuration

  • options (Hash) (defaults to: nil)

    to merge after loading config hash

Returns:

  • (Config)

    configuration object

Raises:

  • (ArgumentError)

    on failure to load



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
# File 'lib/right_develop/testing/recording/config.rb', line 187

def self.from_file(path, options = nil)
  # load
  unless ::File.file?(path)
    raise ConfigError, "Missing expected configuration file: #{path.inspect}"
  end
  config_hash = deep_mash(::YAML.load_file(path))

  # enumerate routes looking for any route-specific config data to load
  # into the config hash from .yml files in subdirectories. this allows
  # the user to spread configuration of specific requests/responses out in
  # the file system instead of having a single monster config .yml
  extension = '.yml'
  (config_hash[:routes] || {}).each do |route_path, route_data|
    if subdir = route_data[:subdir]
      route_subdir = ::File.expand_path(::File.join(path, '..', subdir))
      ::Dir[::File.join(route_subdir, "**/*#{extension}")].each do |route_config_path|
        route_config_data = ::Mash.new(::YAML.load_file(route_config_path))
        filename = ::File.basename(route_config_path)[0..-(extension.length + 1)]
        hash_path = ::File.dirname(route_config_path)[(route_subdir.length + 1)..-1].split('/')
        unless current_route_data = ::RightSupport::Data::HashTools.deep_get(route_data, hash_path)
          current_route_data = ::Mash.new
          ::RightSupport::Data::HashTools.deep_set!(route_data, hash_path, current_route_data)
        end

        # inject a 'stop' at the point where the sub-config file data was
        # inserted into the big hash. the 'stop' basically distingishes the
        # 'directory' from the 'file' information because the hash doesn't
        # use classes to distinguish the data it contains; it only uses
        # simple types. use of simple types makes it easy to YAMLize or
        # JSONize or otherwise serialize in round-trip fashion.
        merge_data = { filename => { STOP_TRAVERSAL_KEY => route_config_data } }
        ::RightSupport::Data::HashTools.deep_merge!(current_route_data, merge_data)
      end
    end
  end

  # config
  self.new(config_hash, options)
end

Instance Method Details

#fixtures_dir(value = nil) ⇒ String

Returns location of fixtures used for record/playback.

Returns:

  • (String)

    location of fixtures used for record/playback



108
109
110
111
# File 'lib/right_develop/testing/recording/config.rb', line 108

def fixtures_dir(value = nil)
  @config_hash['fixtures_dir'] = value if value
  @config_hash['fixtures_dir']
end

#log_dir(value = nil) ⇒ Object



163
164
165
166
# File 'lib/right_develop/testing/recording/config.rb', line 163

def log_dir(value = nil)
  @config_hash['log_dir'] = value if value
  @config_hash['log_dir']
end

#log_level(value = nil) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/right_develop/testing/recording/config.rb', line 146

def log_level(value = nil)
  if value
    case value
    when Integer
      if value < ::Logger::DEBUG || value >= ::Logger::UNKNOWN
        raise ConfigError, "log_level is out of range: #{value}"
      end
      @config_hash['log_level'] = value
    when String, Symbol
      @config_hash['log_level'] = ::Logger.const_get(value.to_s.upcase)
    else
      raise ConfigError, "log_level is unexpected type: #{log_level}"
    end
  end
  @config_hash['log_level']
end

#mode(value = nil) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/right_develop/testing/recording/config.rb', line 113

def mode(value = nil)
  if value
    value = value.to_s
    if value.empty?
      raise ConfigError, "#{MODE_ENV_VAR} must be set"
    elsif VALID_MODES.has_key?(value)
      @config_hash['mode'] = value.to_sym
    else
      raise ConfigError, "mode must be one of #{VALID_MODES.keys.sort.inspect}: #{value.inspect}"
    end
  end
  @config_hash['mode']
end

#routes(value = nil) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/right_develop/testing/recording/config.rb', line 127

def routes(value = nil)
  if value
    case value
    when Hash
      # normalize routes for efficient usage but keep them separate from
      # user's config so that .to_hash returns something understandable and
      # JSONizable/YAMLable.
      @normalized_routes = value.inject(::Mash.new) do |r, (k, v)|
        r[normalize_route_prefix(k)] = normalize_route_data(k, v)
        r
      end
      @config_hash['routes'] = ::RightSupport::Data::HashTools.deep_clone(value)
    else
      raise ConfigError, 'routes must be a hash'
    end
  end
  @normalized_routes
end

#throttle(value = nil) ⇒ Object



168
169
170
171
172
173
174
175
176
177
# File 'lib/right_develop/testing/recording/config.rb', line 168

def throttle(value = nil)
  if value
    value = Integer(value)
    if value < 0 || value > 100
      raise ConfigError, "throttle is out of range: #{value}"
    end
    @config_hash['throttle'] = value
  end
  @config_hash['throttle']
end

#to_hashString

Returns raw hash representing complete configuration.

Returns:

  • (String)

    raw hash representing complete configuration



102
103
104
105
# File 'lib/right_develop/testing/recording/config.rb', line 102

def to_hash
  # unmash to hash
  ::JSON.load(@config_hash.to_json)
end