brine-dsl 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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