brine-dsl 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90f218b8154747a97c408360872b4dd8ba4a3be9
4
- data.tar.gz: cd5eefa092fd1177ee60feb2960c234f29c079b1
3
+ metadata.gz: 690350fecddec6e5978bcb684189f2ac742a7918
4
+ data.tar.gz: 6a37f34f8424ac9422517f3cc0282cb8eb5b321d
5
5
  SHA512:
6
- metadata.gz: 182d3f8d311d2fa0b3c9dda73e6538de268492d1b0610b1f4cd826402e949f8d55d85e52216cedd3e1673e6ecc599aefb4b16e2a017070b000b59178ea4c5d82
7
- data.tar.gz: 1645d45e940081720a945904248cb647e5296a032e01b04ded450e8b6612cb0c8060d4f68ec2966ca8c661ef2c5078cb24a8c69b4f6384993b431ecc1b88d025
6
+ metadata.gz: 41b4ddf87394b647485b3ae8f77c517742661330583650eebf980e52050b2b8878f6d27c150613682e4da04b3ad0c109c62d97018a36effcb698e4f5bda3caae
7
+ data.tar.gz: 786aa5afef5cca973ac32c12b5fac728810da1d1eeface795254ac9d61f2b1f14b6fb4978c7f14463616f7342f45e30aabe02b566e32af09b64e9e2687a9e326
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brine-dsl (0.9.0)
4
+ brine-dsl (0.10.2.pre.SNAPSHOT)
5
5
  cucumber (~> 2.4)
6
6
  faraday (~> 0.12)
7
7
  faraday_middleware (~> 0.12)
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
  require 'bundler'
3
3
  require 'cucumber/rake/task'
4
4
 
5
- Cucumber::Rake::Task.new(:check) do |t|
5
+ Cucumber::Rake::Task.new(:check) do |t|
6
6
  # Cucumber needs some help to deal with the features and support being split.
7
7
  t.libs = "#{__dir__}"
8
8
  end
data/brine-dsl.gemspec CHANGED
@@ -2,8 +2,7 @@
2
2
  raise 'VERSION must be specified in environment' unless ENV['VERSION']
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'brine-dsl'
5
- s.version = '0.9.0'
6
- # s.version = ENV['VERSION']
5
+ s.version = ENV['VERSION'][1..-1]
7
6
  s.platform = Gem::Platform::RUBY
8
7
  s.authors = ["Matt Whipple"]
9
8
  s.email = ["mwhipple@brightcove.com"]
@@ -0,0 +1,120 @@
1
+ ##
2
+ # @file cleaning_up.rb
3
+ # Clean up resources created during test run.
4
+ #
5
+ # Will issue DELETE call for all tracked URLs which will normally be triggered
6
+ # in a hook.
7
+ #
8
+ # The present approach for this is to explicitly track created resources to
9
+ # which DELETE calls will be sent. Cleaning up of resources will be given some
10
+ # further attention in the future, but this functionality should be preserved.
11
+ ##
12
+
13
+ module Brine
14
+
15
+ ##
16
+ # A module providing resource cleanup.
17
+ #
18
+ # Exposes methods to keep a stack of DeleteCommands corresponding to each
19
+ # created resource which are then popped and invoked to perform the cleanup.
20
+ #
21
+ # LIFO behavior is adopted as it is more likely to preserve integrity,
22
+ # such as removing children added to parents or similar dependencies.
23
+ ##
24
+ module CleaningUp
25
+
26
+ ##
27
+ # A command object for the delete which will be executed as part of cleaning up.
28
+ #
29
+ # The command will be retried a specified number of times if an unsuccessful status code is received.
30
+ ##
31
+ class DeleteCommand
32
+
33
+ ##
34
+ # Construct a command with the required paramters to perform the delete.
35
+ #
36
+ # @param [Faraday::Connection, #delete] client The Faraday client which will send the delete message.
37
+ # @param [String] path The path of the resource to be deleted.
38
+ # @param [Array<Integer>] oks The response status codes which will be considered successful.
39
+ # @param [Integer] attempts The number of times this command should be tried.
40
+ ##
41
+ def initialize(client, path, oks: [200,204], attempts: 3)
42
+ @client = client
43
+ @path = path
44
+ @oks = oks
45
+ @attempts = attempts
46
+ end
47
+
48
+ ##
49
+ # Issue the delete based on the parameters provided during construction.
50
+ #
51
+ # @return [Boolean] true if a successful response is obtained, otherwise false.
52
+ ##
53
+ def cleanup
54
+ for _ in 1..@attempts
55
+ begin
56
+ resp=@client.delete(@path)
57
+ return true if @oks.include?(resp.status)
58
+ rescue ex
59
+ STDERR.puts "WARNING: #{ex}"
60
+ end
61
+ end
62
+ STDERR.puts "ERROR: Could not DELETE #{@path}"
63
+ false
64
+ end
65
+ end
66
+
67
+ ##
68
+ # Set the Faraday HTTP client object used to issue DELETE calls.
69
+ #
70
+ # The client provided will be subsequently used to create DeleteCommands.
71
+ # This can be called multiple times where each DeleteCommand will use the
72
+ # most recently set value. In most use cases this will also be the client
73
+ # used to issue the creation requests and could therefore be passed to this
74
+ # method prior to use.
75
+ #
76
+ # @param [Faraday::Connection, #delete] client The client to use to DELETE subsequently tracked resources.
77
+ ##
78
+ def set_cleaning_client(client)
79
+ @client = client
80
+ end
81
+
82
+ ##
83
+ # Record resource to be later cleaned (pushes a DeleteCommand).
84
+ #
85
+ # @param [String] path The path for the created resource; will be issued a DELETE.
86
+ ##
87
+ def track_created_resource(path)
88
+ cleanup_commands << DeleteCommand.new(@client, path)
89
+ end
90
+
91
+ ##
92
+ # Clean recorded resources (normally after a test run).
93
+ #
94
+ # @return [Boolean] true if all commands succeeded successfully, otherwise false.
95
+ ##
96
+ def cleanup_created_resources
97
+ # Avoid the use of any short circuiting folds.
98
+ cleanup_commands.reverse.inject(true) { |accum, x| accum && x.cleanup }
99
+ end
100
+
101
+ private
102
+
103
+ ##
104
+ # The array which serves as the stack of DeleteCommands.
105
+ #
106
+ # Provides the property mixed in by the module.
107
+ #
108
+ # @return [Array<DeleteCommand>]
109
+ ##
110
+ def cleanup_commands
111
+ @cleanup_commands ||= []
112
+ end
113
+
114
+ end
115
+
116
+ ##
117
+ # Mix the CleaningUp module functionality into the main Brine module.
118
+ ##
119
+ include CleaningUp
120
+ end
@@ -0,0 +1,123 @@
1
+ ##
2
+ # @file client_building.rb
3
+ # Construction of a Faraday connection.
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # Supports construction of a Faraday connection with some common middleware.
10
+ ##
11
+ module ClientBuilding
12
+ require 'oauth2'
13
+
14
+ ##
15
+ # Parameter object used to configure OAuth2 middleware
16
+ #
17
+ # This is essentially a thin wrapper around `https://github.com/oauth-xx/oauth2`
18
+ # to provide a mini-DSL and to facilitate the middleware configuration.
19
+ ##
20
+ class OAuth2Params
21
+
22
+ ##
23
+ # A token which has been retrieved from the authorization server.
24
+ ##
25
+ attr_accessor :token
26
+
27
+ ##
28
+ # The type of OAuth2 token which will be retrieved.
29
+ #
30
+ # Currently only `bearer` is supported.
31
+ ##
32
+ attr_accessor :token_type
33
+
34
+ ##
35
+ # Instantiate a new object with default attributes.
36
+ ##
37
+ def initialize
38
+ @token_type = 'bearer'
39
+ end
40
+
41
+ ##
42
+ # Fetch an OAuth2 token based on configuration and store as `token`.
43
+ #
44
+ # The parameters are forwarded to OAuth2::Client.new which is used to
45
+ # retrieve the token.
46
+ #
47
+ # @param [String] id The login id to send to request authorization.
48
+ # @param [String] secret The secret to send to request authorization.
49
+ # @param [Hash] opts Options with which to create a client,
50
+ # see `https://github.com/oauth-xx/oauth2/blob/master/lib/oauth2/client.rb` for full details,
51
+ # common options will be duplicated here.
52
+ # @option opts [String] :site The OAuth2 authorization server from which to request a token.
53
+ # @option opts [String] :token_url The absolute or relative path to the Token endpoint on the authorization server.
54
+ # @option opts [Hash] :ssl SSL options to pass through to the transport client,
55
+ # `{verify: false}` may be useful for self-signed certificates.
56
+ ##
57
+ def fetch_from(id, secret, opts)
58
+ @token = OAuth2::Client.new(id, secret, opts)
59
+ .client_credentials.get_token.token
60
+ end
61
+ end
62
+
63
+ ##
64
+ # Acquire an OAuth2 token within provided configuration block.
65
+ #
66
+ # @param [Block] Logic to execute with an OAuth2Params receiver;
67
+ # this will normally involve an OAuth2Params#fetch_from call.
68
+ ##
69
+ def use_oauth2_token(&block)
70
+ @oauth2 = OAuth2Params.new
71
+ @oauth2.instance_eval(&block)
72
+ end
73
+
74
+ ##
75
+ # The handlers/middleware that will be wired while constructing a client.
76
+ #
77
+ # This is represented as list of functions so that it can be more easily customized for
78
+ # unexpected use cases.
79
+ # It should likely be broken up a bit more sensibly and more useful insertion commands added...
80
+ # but it's likely enough of a power feature and platform specific to leave pretty raw.
81
+ ##
82
+ def connection_handlers
83
+ @connection_handlers ||= [
84
+ proc do |conn|
85
+ conn.request :json
86
+ if @oauth2
87
+ conn.request :oauth2, @oauth2.token, :token_type => @oauth2.token_type
88
+ end
89
+ end,
90
+ proc do |conn|
91
+ if @logging
92
+ conn.response :logger, nil, :bodies => (@logging.casecmp('DEBUG') == 0)
93
+ end
94
+ conn.response :json, :content_type => /\bjson$/
95
+ end,
96
+ proc{|conn| conn.adapter Faraday.default_adapter }
97
+ ]
98
+ end
99
+
100
+ ##
101
+ # Construct a new client to send requests to `host`.
102
+ #
103
+ # Will configure the client using `#connection_handlers`.
104
+ #
105
+ # @param [String] host The hostname to which this client will send requests.
106
+ # @param [String] logging Indicate the desired logging level for this client.
107
+ # @return [Faraday::Connection] The configured client connection.
108
+ ##
109
+ def client_for_host(host, logging: ENV['BRINE_LOG_HTTP'])
110
+ @logging = logging
111
+ Faraday.new(host) do |conn|
112
+ connection_handlers.each{|h| h.call(conn) }
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+ ##
119
+ # Mix the ClientBuilding module functionality into the main Brine module.
120
+ ##
121
+ include ClientBuilding
122
+
123
+ end
@@ -0,0 +1,91 @@
1
+ ##
2
+ # @file coering.rb
3
+ # Type coercion to support assertions.
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # A module which allows mixing in Corcer behavior.
10
+ ##
11
+ module Coercing
12
+
13
+ ##
14
+ # Coerces the types of provided objects to support desired assertions.
15
+ #
16
+ # Coercion is used to support handling richer data types in Brine without
17
+ # introducing noisy type information to the language.
18
+ # Argument Transforms can be used to convert input provided directly
19
+ # to a Brine step, however data extracted from JSON will by default be
20
+ # limited to the small amount of JSON supported data types. As normally
21
+ # the data extracted from JSON will only be directly present on one side
22
+ # of the assertion (most likely the left), the simpler JSON data types can
23
+ # be coerced to a richer type based on the right hand side.
24
+ #
25
+ # A standard example (and that which is defined at the moment here) is date/time
26
+ # values. When testing an API that returns such values it is likely desirable to
27
+ # perform assertions with proper date/time semantics, and the coercer allows
28
+ # such assertions against a value retrieved out of a JSON structure.
29
+ #
30
+ # The combination of Argument Transforms and the Coercer can also effectively
31
+ # allow for user defined types to seemlessly exist within the Brine tests.
32
+ # The date/time implementation is an example of this.
33
+ #
34
+ # Implementation (Most of the non-implementation info should later be moved).
35
+ # ---
36
+ # Internally the Coercer is a wrapper around a map of coercion functions
37
+ # where the keys are the pairs of classes for the operands and the
38
+ # values are the functions which accept a pair of instances (ostensibly) of the
39
+ # classes and return a pair of transformed values. The default coercion function
40
+ # returns the inputs unmodified (a nop), so any pair of classes which does not
41
+ # have a key will pass through unchanged.
42
+ #
43
+ # Note that this relies solely on the hash lookup and does not attempt any kind
44
+ # of inheritance lookup or similar. The current thinking is that there should be
45
+ # few expected types and lineage could be covered through explicit keys so the
46
+ # simple, dumb approach is likely sufficient.
47
+ ##
48
+ class Coercer
49
+
50
+ ##
51
+ # Instantiate a new Coercer.
52
+ #
53
+ # Initialize the standard map of coercion functions.
54
+ ##
55
+ def initialize
56
+ @map = Hash.new(->(lhs, rhs){[lhs, rhs]})
57
+ @map[[String, Time]] = ->(lhs, rhs){[Time.parse(lhs), rhs]}
58
+ end
59
+
60
+ ##
61
+ # Coerce the provided inputs as needed.
62
+ #
63
+ # Looks up and invokes the associated coercion function.
64
+ #
65
+ # @param [Object] first The first operand.
66
+ # @param [Object] second The second operand.
67
+ # @return [Array] A pair of the potentially coerced [first, second] values.
68
+ ##
69
+ def coerce(first, second)
70
+ @map[[first.class, second.class]].call(first, second)
71
+ end
72
+ end
73
+
74
+ ##
75
+ # The coercer instance mixed in as a property.
76
+ #
77
+ # Will instantiate a Coercer if needed on first access.
78
+ #
79
+ # @return [Coercer] The object which will be used to handle coercions.
80
+ ##
81
+ def coercer
82
+ @coercer ||= Coercer.new
83
+ end
84
+
85
+ end
86
+
87
+ ##
88
+ # Include the Coercing module functionality into the main Brine module.
89
+ ##
90
+ include Coercing
91
+ end
data/lib/brine/hooks.rb CHANGED
@@ -1,4 +1,11 @@
1
- # Call CleanerUpper after test run
1
+ ##
2
+ # @file hooks.rb
3
+ # Cucumber hook callbacks used by Brine.
4
+ ##
5
+
6
+ ##
7
+ # Call CleanerUpper after the test run.
8
+ ##
2
9
  After do
3
10
  cleanup_created_resources
4
11
  end
@@ -0,0 +1,51 @@
1
+ ##
2
+ # @file mustache_expanding.rb
3
+ # Support for expanding Mustache templates with a defined binding.
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # Provides a binding environment and template expansion that uses that environment.
10
+ ##
11
+ module MustacheExpanding
12
+ require 'mustache'
13
+
14
+ ##
15
+ # Mutable hash which serves as binding environment.
16
+ #
17
+ # @return [Hash<String, Object>] The active binding environment.
18
+ ##
19
+ def binding
20
+ @binding ||= {}
21
+ end
22
+
23
+ ##
24
+ # Assign `value' to `key' within binding,
25
+ #
26
+ # @param [String] key The key in the binding to which `value` will be assigned.
27
+ # @param [Object] value The value to assign to ``key` within the binding.
28
+ ##
29
+ def bind(key, value)
30
+ STDERR.puts "Assigning #{value} to #{key}" if ENV['BRINE_LOG_BINDING']
31
+ binding[key] = value
32
+ end
33
+
34
+ ##
35
+ # Expanded Mustache template using binding environment.
36
+ # Mustache in...no Mustache out.
37
+ #
38
+ # @param [String] template Template content to expand with binding.
39
+ # @return [String] The contents of `template` with any expansions done using `binding`.
40
+ ##
41
+ def shave_value(template)
42
+ Mustache.render(template, binding)
43
+ end
44
+
45
+ end
46
+
47
+ ##
48
+ # Include the MustacheExpanding module functionality into the main Brine module.
49
+ ##
50
+ include MustacheExpanding
51
+ end
@@ -0,0 +1,159 @@
1
+ ##
2
+ # @file performing.rb
3
+ # Performing of of potentially deferred actions.
4
+ ##
5
+
6
+ module Brine
7
+
8
+ ##
9
+ # A module supporting either immediate or defered evaluation of logic.
10
+ ##
11
+ module Performing
12
+
13
+ ##
14
+ # A passthrough performer which immediately invokes provided actions.
15
+ #
16
+ # This has no instance state and therefore a Flyweight could be used,
17
+ # but too few instances are expected to warrant even the minor divergence.
18
+ ##
19
+ class ImmediatePerformer
20
+
21
+ ##
22
+ # Perform the provided actions immediately.
23
+ #
24
+ # @param [Proc] A thunk of actions to be performed.
25
+ ##
26
+ def perform(actions)
27
+ actions.call
28
+ end
29
+ end
30
+
31
+ ##
32
+ # A Peformer which collects rather than evaluating actions.
33
+ ##
34
+ class CollectingPerformer
35
+
36
+ ##
37
+ # Construct an instance with an empty collection of actions.
38
+ ##
39
+ def initialize
40
+ @actions = []
41
+ end
42
+
43
+ ##
44
+ # Collect provided actions for later evaluation.
45
+ #
46
+ # @param [Proc] A thunk of actions to be performed.
47
+ ##
48
+ def perform(actions)
49
+ @actions << actions
50
+ end
51
+
52
+ ##
53
+ # Evaluate the collected actions in sequence.
54
+ ##
55
+ def evaluate
56
+ @actions.each { |it| it.call }
57
+ end
58
+
59
+ end
60
+
61
+ ##
62
+ # The currently active Performer instance exposed as a property.
63
+ #
64
+ # The default implementation will be wired as needed upon first access.
65
+ #
66
+ # @return [Performer, #perform] The Performer to which actions will be sent.
67
+ ##
68
+ def performer
69
+ @performer || reset_performer
70
+ end
71
+
72
+ ##
73
+ # Reset the Performer instance to the default implementation.
74
+ #
75
+ # @return [Performer, #perform] The default implementation which will now be the `performer`.
76
+ ##
77
+ def reset_performer
78
+ @performer = ImmediatePerformer.new
79
+ end
80
+
81
+ ##
82
+ # Pass the actions to the current Performer instance.
83
+ #
84
+ # @param [Proc] The thunk of the actions to be performed.
85
+ ##
86
+ def perform(&actions)
87
+ performer.perform(actions)
88
+ end
89
+
90
+ ##
91
+ # Begin collecting, rather than immediately performing, actions.
92
+ ##
93
+ def collect_actions
94
+ @performer = CollectingPerformer.new
95
+ end
96
+
97
+ ##
98
+ # The number of seconds between polling attempts.
99
+ #
100
+ # Can be provided by the `BRINE_POLL_INTERVAL_SECONDS` environment variable.
101
+ # Defaults to `0.5`.
102
+ #
103
+ # @return [Number] The number of seconds to sleep between poll attempts.
104
+ ##
105
+ def poll_interval_seconds
106
+ ENV['BRINE_POLL_INTERVAL_SECONDS'] || 0.25
107
+ end
108
+
109
+ ##
110
+ # Retry the provided block for the specified period.
111
+ #
112
+ # If the provided block is evaluated successfully the result
113
+ # will be returned, if any exception is thrown it will be
114
+ # retried until the period elapses.
115
+ #
116
+ # @param [Number] seconds The period (in seconds) during which the block will be retried.
117
+ # @param [Number] interval How long to sleep between polling attempts, defaults to `#poll_interval_seconds`.
118
+ # @param [Block] The logic to retry within the defined period.
119
+ # @return [Object] The result of the block if successfully evaluated.
120
+ # @throws [Exception] The most recent exception thrown from the block if never successfully evaluated.
121
+ ##
122
+ def poll_for(seconds, interval=poll_interval_seconds)
123
+ failure = nil
124
+ quit = Time.now + seconds
125
+ while (Time.now < quit)
126
+ begin
127
+ return yield
128
+ rescue Exception => ex
129
+ failure = ex
130
+ sleep interval
131
+ end
132
+ end
133
+ raise failure
134
+ end
135
+
136
+ ##
137
+ # The duration in seconds for the given handle.
138
+ #
139
+ # Currently this only supports values provided through environment variables
140
+ # of the format BRINE_DURATION_SECONDS_{handle}.
141
+ #
142
+ # @param[String] duration The handle/name of the duration whose length should be returned.
143
+ # @return [Number] The number of seconds to poll for the requested duration.
144
+ ##
145
+ def retrieve_duration(duration)
146
+ if ENV["BRINE_DURATION_SECONDS_#{duration}"]
147
+ ENV["BRINE_DURATION_SECONDS_#{duration}"].to_f
148
+ else
149
+ STDERR.puts("Duration #{duration} not defined")
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ ##
156
+ # Mix the Performing module functionality into the main Brine module.
157
+ ##
158
+ include Performing
159
+ end