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.
@@ -0,0 +1,88 @@
1
+ ##
2
+ # @file type_checking.rb
3
+ # Checks whether provided values are instances of a specified type.
4
+ #
5
+ # Provides validation for an extended set of types supported byJSON.
6
+ ##
7
+
8
+ module Brine
9
+
10
+ ##
11
+ # Module which adds functionality to check the type of values.
12
+ ##
13
+ module TypeChecking
14
+
15
+ require 'rspec/expectations'
16
+
17
+ ##
18
+ # A registry of checks for types which can return a Matcher for provided types.
19
+ ##
20
+ class TypeChecks
21
+ include RSpec::Matchers
22
+
23
+ ##
24
+ # Initialize an instance with default checks or those provided.
25
+ #
26
+ # @param [Hash<String, Matcher>] A hash of Matchers by type, where the Matcher value will be
27
+ # used as the Matcher for the specified type.
28
+ # It is expected that no map will be provided and the default
29
+ # mapping will therefore be used.
30
+ ##
31
+ def initialize(map={Object: be_a_kind_of(Hash),
32
+ String: be_a_kind_of(String),
33
+ Number: be_a_kind_of(Numeric),
34
+ Integer: be_a_kind_of(Integer),
35
+ Array: be_a_kind_of(Array),
36
+ DateTime: be_a_kind_of(Time),
37
+ Boolean: satisfy{|it| it == true || it == false } })
38
+ @map = map
39
+ end
40
+
41
+ ##
42
+ # Return the Matcher for the specified type or die if not present.
43
+ #
44
+ # @param [Class] type The type whose Matcher should be returned.
45
+ # @return [RSpec::Matcher] The Matcher configured for `type`.
46
+ # @throw Exception if no Matcher exists for `type`.
47
+ ##
48
+ def for_type(type)
49
+ @map[type.to_sym] || raise("Unsupported type #{type}")
50
+ end
51
+
52
+ ##
53
+ # Register the provided matcher for the specified type.
54
+ #
55
+ # @param[Class] type The type for which the Matcher will be registered.
56
+ # @param[RSpec::Matcher] matcher A matcher to verify that input is an instance of type.
57
+ ##
58
+ def register_matcher(type, matcher)
59
+ @map[type.to_sym] = matcher
60
+ end
61
+ end
62
+
63
+ ##
64
+ # The currently active TypeCheck instance as a property, instantiating as needed.
65
+ ##
66
+ def type_checks
67
+ @type_check ||= TypeChecks.new
68
+ end
69
+
70
+ ##
71
+ # Return the Matcher for the specified type.
72
+ #
73
+ # This is the primary interface for type_checking to the rest of the system,
74
+ # and is the only one expected to be used during test execution.
75
+ #
76
+ # @param [Class] type The type for which a Matcher should be returned.
77
+ # @return [RSpec::Matcher] The Matcher currently registered for the type.
78
+ ##
79
+ def type_check_for(type)
80
+ type_checks.for_type(type)
81
+ end
82
+ end
83
+
84
+ ##
85
+ # Mix the TypeChecking module functionality into the main Brine module.
86
+ ##
87
+ include TypeChecking
88
+ end
data/lib/brine/util.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # Assorted utility functions
2
+ # The deprecation piece should be extracted and the rest considered dead.
2
3
  module BrineUtil
3
4
  # reevaluate passed block until it returns without throwing an exception
4
5
  # or `time` elapses; retry every `interval`
data/lib/brine.rb CHANGED
@@ -5,40 +5,33 @@
5
5
  # The primary goal of this file is to load all resources needed to use brine.
6
6
  # A secondary goal which should inform how that is done is that loading this
7
7
  # file by itself should provide new objects but not otherwise impact existing
8
- # state such as by modifying the World or defining any Steps, Transforms, etc.
8
+ # state such as by modifying the World or defining any Steps, Transforms, etc.
9
9
  # Those types of changes should be done by @ref #brine_mix.
10
-
11
- require 'brine/cleaner_upper'
12
- require 'brine/mustache_binder'
13
- require 'brine/requester'
14
- require 'brine/util'
15
- require 'brine/selector'
16
- require 'brine/type_checks'
17
-
18
10
  ##
19
- # Meta-module for modules to mix into World.
20
- module Brine
21
- include CleanerUpper
22
- include MustacheBinder
23
- include Requesting
24
- include BrineUtil
25
- include Selection
26
- include TypeChecking
27
- end
11
+
12
+ require 'brine/cleaning_up'
13
+ require 'brine/mustache_expanding'
14
+ require 'brine/performing'
15
+ require 'brine/requesting'
16
+ require 'brine/selecting'
17
+ require 'brine/type_checking'
28
18
 
29
19
  ##
30
- # Load the files with side effects and return @ref Brine.
20
+ # Load the files with side effects.
31
21
  #
32
22
  # Expected to be called as `World(brine_mix)`
23
+ # @return [module] The `Brine` module.
24
+ ##
33
25
  def brine_mix
34
26
  require 'brine/step_definitions/assignment'
35
27
  require 'brine/step_definitions/request_construction'
36
28
  require 'brine/step_definitions/assertions'
37
29
  require 'brine/step_definitions/cleanup'
30
+ require 'brine/step_definitions/perform'
38
31
  require 'brine/step_definitions/selection'
39
32
 
40
33
  require 'brine/transforms'
41
- require 'brine/rest_steps'
42
34
  require 'brine/hooks'
35
+
43
36
  Brine
44
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brine-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Whipple
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-28 00:00:00.000000000 Z
11
+ date: 2019-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -137,21 +137,24 @@ files:
137
137
  - brine-dsl.gemspec
138
138
  - feature_setup.rb
139
139
  - lib/brine.rb
140
- - lib/brine/cleaner_upper.rb
141
- - lib/brine/coercer.rb
140
+ - lib/brine/cleaning_up.rb
141
+ - lib/brine/client_building.rb
142
+ - lib/brine/coercing.rb
142
143
  - lib/brine/hooks.rb
143
- - lib/brine/mustache_binder.rb
144
- - lib/brine/requester.rb
144
+ - lib/brine/mustache_expanding.rb
145
+ - lib/brine/performing.rb
146
+ - lib/brine/requesting.rb
145
147
  - lib/brine/rest_steps.rb
146
- - lib/brine/selector.rb
148
+ - lib/brine/selecting.rb
147
149
  - lib/brine/step_definitions/assertions.rb
148
150
  - lib/brine/step_definitions/assignment.rb
149
151
  - lib/brine/step_definitions/cleanup.rb
152
+ - lib/brine/step_definitions/perform.rb
150
153
  - lib/brine/step_definitions/request_construction.rb
151
154
  - lib/brine/step_definitions/selection.rb
152
155
  - lib/brine/test_steps.rb
153
156
  - lib/brine/transforms.rb
154
- - lib/brine/type_checks.rb
157
+ - lib/brine/type_checking.rb
155
158
  - lib/brine/util.rb
156
159
  homepage: http://github.com/brightcove/brine
157
160
  licenses:
@@ -173,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
176
  version: '0'
174
177
  requirements: []
175
178
  rubyforge_project:
176
- rubygems_version: 2.5.2.3
179
+ rubygems_version: 2.5.1
177
180
  signing_key:
178
181
  specification_version: 4
179
182
  summary: Cucumber@REST in Brine
@@ -1,101 +0,0 @@
1
- ##
2
- # @file cleaner_upper.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
- # A command object for the delete which will be executed as part of cleaning up.
14
- class DeleteCommand
15
-
16
- ##
17
- # Construct a command with the required paramters to perform the delete.
18
- #
19
- # @param client The Faraday client which will send the delete message.
20
- # @param path The path of the resource to be deleted.
21
- # @param oks The response status codes which will be considered successful.
22
- # @param attempts The number of times this command should be tried,
23
- # retrying if an unsuccessful status code is received.
24
- def initialize(client, path, oks: [200,204], attempts: 3)
25
- @client = client
26
- @path = path
27
- @oks = oks
28
- @attempts = attempts
29
- end
30
-
31
- ##
32
- # Issue the delete based on the parameters provided during construction.
33
- #
34
- # @returns true if a successful response is obtained, otherwise false.
35
- def cleanup
36
- while @attempts > 0
37
- begin
38
- resp=@client.delete(@path)
39
- return true if @oks.include?(resp.status)
40
- rescue ex
41
- puts "WARNING: #{ex}"
42
- end
43
- @attempts -= 1
44
- end
45
- puts "ERROR: Could not DELETE #{@path}"
46
- false
47
- end
48
- end
49
-
50
- ##
51
- # A mixin which provides resource cleanup.
52
- #
53
- # Exposes methods to keep a stack of DeleteCommands corresponding to each
54
- # created resource which are then popped and invoked to perform the cleanup.
55
- #
56
- # The LIFO behavior is adopted as it is more likely to preserve integrity,
57
- # such as removing children added to parents or similar dependencies.
58
- module CleanerUpper
59
-
60
- ##
61
- # Set the Faraday HTTP client object used to issue DELETE calls.
62
- #
63
- # The client provided will be subsequently used to create DeleteCommands.
64
- # This can be called multiple times where each DeleteCommand will use the
65
- # most recently set value. In most use cases this will also be the client
66
- # used to issue the creation requests and could therefore be passed to this
67
- # method prior to use.
68
- #
69
- # @param client - The client to use to DELETE subsequently tracked resources.
70
- def set_cleaning_client(client)
71
- @client = client
72
- end
73
-
74
- ##
75
- # Record resource to be later cleaned (pushes a DeleteCommand).
76
- #
77
- # @param path - The path for the created resource, will be issued a DELETE.
78
- def track_created_resource(path)
79
- created_resources << DeleteCommand.new(@client, path)
80
- end
81
-
82
- ##
83
- # Clean recorded resources (normally after a test run).
84
- def cleanup_created_resources
85
- created_resources.reverse.each{|it| it.cleanup}
86
- end
87
-
88
- private
89
-
90
- ##
91
- # The array which serves as the stack of DeleteCommands.
92
- #
93
- # Works as a "module provided property" which is a name I
94
- # may have just made up.
95
- #
96
- # TODO: Find proper term for module provided property
97
- # TODO: The name of this property seems sloppy as it contains commands.
98
- def created_resources
99
- @created_resources ||= []
100
- end
101
- end
data/lib/brine/coercer.rb DELETED
@@ -1,68 +0,0 @@
1
- ##
2
- # @file coercer.rb
3
- # Type coercion to support assertions.
4
-
5
- ##
6
- # Coerces the types of provided objects to support desired assertions.
7
- #
8
- # Coercion is used to support handling richer data types in Brine without
9
- # introducing noisy type information to the language.
10
- # Argument Transforms can be used to convert input provided directly
11
- # to a Brine step, however data extracted from JSON will by default be
12
- # limited to the small amount of JSON supported data types. As normally
13
- # the data extracted from JSON will only be directly present on one side
14
- # of the assertion (most likely the left), the simpler JSON data types can
15
- # be coerced to a richer type based on the right hand side.
16
- #
17
- # A standard example (and that which is defined at the moment here) is date/time
18
- # values. When testing an API that returns such values it is likely desirable to
19
- # perform assertions with proper date/time semantics, and the coercer allows
20
- # such assertions against a value retrieved out of a JSON structure.
21
- #
22
- # The combination of Argument Transforms and the Coercer can also effectively
23
- # allow for user defined types to seemlessly exist within the Brine tests.
24
- # The date/time implementation is an example of this.
25
- # TODO: Document this in a friendlier place, with a contrived example.
26
- # TODO: Having the default Time stuff should likely be removed for v2.
27
- #
28
- # Implementation (Most of the non-implementation info should later be moved).
29
- # ---
30
- # Internally the Coercer is a wrapper around a map of coercion functions
31
- # where the keys are the pairs of classes for the operands and the
32
- # values are the functions which accept a pair of instances (ostensibly) of the
33
- # classes and return a pair of transformed values. The default coercion function
34
- # returns the inputs unmodified (a nop), so any pair of classes which does not
35
- # have a key will pass through unchanged.
36
- #
37
- # Note that this relies solely on the hash lookup and does not attempt any kind
38
- # of inheritance lookup or similar. The current thinking is that there should be
39
- # few expected types and lineage could be covered through explicit keys so the
40
- # simple, dumb approach is likely sufficient.
41
- class Coercer
42
- ##
43
- # Instantiate a new Coercer.
44
- #
45
- # Initialize the standard map of coercion functions.
46
- def initialize
47
- @map = Hash.new(->(lhs, rhs){[lhs, rhs]})
48
- @map[[String, Time]] = ->(lhs, rhs){[Time.parse(lhs), rhs]}
49
- end
50
-
51
- ##
52
- # Coerce the provided inputs as needed.
53
- #
54
- # Looks up and invokes the associated coercion function.
55
- #
56
- # @param lhs - The left hand side input.
57
- # @param rhs - The right hand side input.
58
- # @returns A pair (2 element array) of potentially coerced values.
59
- def coerce(lhs, rhs)
60
- @map[[lhs.class, rhs.class]].call(lhs, rhs)
61
- end
62
- end
63
-
64
- module Coercion
65
- def coercer
66
- @coercer ||= Coercer.new
67
- end
68
- end
@@ -1,25 +0,0 @@
1
- require 'mustache'
2
-
3
- # Provides a binding environment and template expansion functions that use that
4
- # environment
5
- module MustacheBinder
6
-
7
- # getter for mutable hash which serves as binding environment
8
- def binding
9
- @binding ||= {}
10
- end
11
-
12
- # assign `value' to `key' within binding
13
- # defined in a method to allow observability/interception
14
- def bind(key, value)
15
- puts "Assigning #{value} to #{key}" if ENV['BRINE_LOG_BINDING']
16
- binding[key] = value
17
- end
18
-
19
- # return value as expanded Mustache template using binding environment
20
- # Mustache in...no Mustache out
21
- def shave_value(val)
22
- Mustache.render(val, binding)
23
- end
24
-
25
- end
@@ -1,161 +0,0 @@
1
- ##
2
- # @file requester.rb
3
- # Request construction and response storage
4
- ##
5
-
6
- require 'oauth2'
7
- require 'faraday_middleware'
8
- require 'brine/util'
9
-
10
- ##
11
- # The root url to which Brine will send requests.
12
- #
13
- # This will normally be the value of ENV['BRINE_ROOT_URL'],
14
- # and that value should be directly usable after older
15
- # ENV['ROOT_URL'] is end-of-lifed (at which point this can be removed.
16
- #
17
- # @return [String] The root URL to use or nil if none is provided.
18
- ##
19
- def brine_root_url
20
- if ENV['BRINE_ROOT_URL']
21
- ENV['BRINE_ROOT_URL']
22
- elsif ENV['ROOT_URL']
23
- deprecation_message('1.0', 'ROOT_URL is deprecated, replace with BRINE_ROOT_URL') if ENV['ROOT_URL']
24
- ENV['ROOT_URL']
25
- end
26
- end
27
-
28
- ##
29
- # Parameter object used to configure OAuth2 middleware
30
- #
31
- # Also used to provide basic DSL for configuration
32
- ##
33
- class OAuth2Params
34
- attr_accessor :token, :token_type
35
-
36
- def initialize
37
- @token_type = 'bearer'
38
- end
39
-
40
- def fetch_from(id, secret, opts)
41
- @token = OAuth2::Client.new(id, secret, opts)
42
- .client_credentials.get_token.token
43
- end
44
- end
45
-
46
- # Construct a Faraday client to be used to send built requests
47
- module ClientBuilding
48
-
49
- # authenticate using provided info and save token for use in later requests
50
- def use_oauth2_token(&block)
51
- @oauth2 = OAuth2Params.new
52
- @oauth2.instance_eval(&block)
53
- end
54
-
55
- def with_oauth2_token(&block)
56
- use_oauth2_token(&block)
57
- self
58
- end
59
-
60
- # This is represented as list of functions so that it can be more easily customized for
61
- # unexpected use cases.
62
- # It should likely be broken up a bit more sensibly and more useful insertion commands added...
63
- # but it's likely enough of a power feature and platform specific to leave pretty raw.
64
- def connection_handlers
65
- @connection_handlers ||= [
66
- proc do |conn|
67
- conn.request :json
68
- if @oauth2
69
- conn.request :oauth2, @oauth2.token, :token_type => @oauth2.token_type
70
- end
71
- end,
72
- proc do |conn|
73
- if @logging
74
- conn.response :logger, nil, :bodies => (@logging.casecmp('DEBUG') == 0)
75
- end
76
- conn.response :json, :content_type => /\bjson$/
77
- end,
78
- proc{|conn| conn.adapter Faraday.default_adapter }
79
- ]
80
- end
81
-
82
- def client_for_host(host, logging: ENV['BRINE_LOG_HTTP'])
83
- @logging = logging
84
- Faraday.new(host) do |conn|
85
- connection_handlers.each{|h| h.call(conn) }
86
- end
87
- end
88
- end
89
-
90
- class ClientBuilder
91
- include ClientBuilding
92
- end
93
-
94
- # Module in charge of constructing requests and saving responses
95
- module Requesting
96
- include ClientBuilding
97
-
98
- # Utility Methods
99
- #
100
- # Normalize an HTTP method passed from a specification into a form
101
- # expected by the HTTP client library (lowercased symbol)
102
- def parse_method(method)
103
- method.downcase.to_sym
104
- end
105
-
106
- def set_client(client)
107
- @client = client
108
- end
109
-
110
- # return Faraday client object so that it could be used directly
111
- # or passed to another object
112
- def client
113
- @client ||= client_for_host(brine_root_url || 'http://localhost:8080',
114
- logging: ENV['BRINE_LOG_HTTP'])
115
- end
116
-
117
- # clear out any previously built request state and set defaults
118
- def reset_request
119
- @params = @headers = @body = nil
120
- end
121
-
122
- # store the provided body in the request options being built
123
- # overriding any previously provided object
124
- def set_request_body(obj)
125
- @body = obj
126
- end
127
-
128
- # send a request using method to url using whatever options
129
- # have been built for the present request
130
- def send_request(method, url)
131
- @response = client.run_request(method, url, @body, headers) do |req|
132
- req.params = params
133
- end
134
- end
135
-
136
- # getter for the latest response returned
137
- def response
138
- @response
139
- end
140
-
141
- def headers
142
- @headers ||= {content_type: 'application/json'}
143
- end
144
-
145
- def add_header(k, v)
146
- headers[k] = v
147
- end
148
-
149
- def params
150
- @params ||= {}
151
- end
152
-
153
- def add_request_param(k, v)
154
- params[k] = v
155
- end
156
-
157
- end
158
-
159
- class Requester
160
- include Requesting
161
- end
@@ -1,66 +0,0 @@
1
- require 'brine/coercer'
2
- require 'rspec/expectations'
3
- require 'jsonpath'
4
-
5
- # Selectors here are small wrappers around RSpec
6
- # expectation behavior to encapsulate variations in
7
- # some expecation associated behavior in classes.
8
- class Selector
9
- include RSpec::Matchers
10
- attr_accessor :coercer
11
-
12
- def initialize(target, negated)
13
- @target = target
14
- @message = negated ? :to_not : :to
15
- end
16
-
17
- def filter_matcher(matcher)
18
- matcher
19
- end
20
-
21
- def assert_that(value)
22
- target, value = coercer.coerce(@target, value)
23
- matcher = filter_matcher(yield(value))
24
- expect(target).send(@message, matcher)
25
- end
26
- end
27
-
28
- class AnySelector < Selector
29
- def filter_matcher(matcher)
30
- include(matcher)
31
- end
32
- end
33
-
34
- class AllSelector < Selector
35
- def filter_matcher(matcher)
36
- all(matcher)
37
- end
38
- end
39
-
40
- #
41
- # Module
42
- #
43
- module Selection
44
- include Coercion
45
- attr_reader :selector
46
-
47
- def use_selector(selector)
48
- selector.coercer = coercer
49
- @selector = selector
50
- end
51
- end
52
-
53
- #
54
- # Steps
55
- #
56
- RESPONSE_ATTRIBUTES='(status|headers|body)'
57
- Then(/^the value of `([^`]*)` is( not)? (.*)$/) do |value, negated, assertion|
58
- use_selector(Selector.new(value, (!negated.nil?)))
59
- step "it is #{assertion}"
60
- end
61
-
62
- def dig_from_response(attribute, path=nil, plural=false)
63
- root = response.send(attribute.to_sym)
64
- return root if !path
65
- JsonPath.new("$.#{path}").send(plural ? :on : :first, root)
66
- end
@@ -1,36 +0,0 @@
1
- # type_checks.rb -- checks whether provided is a valid instance of a specified type
2
- #
3
- # Provides validation for standard JSON type
4
-
5
- require 'rspec/expectations'
6
-
7
- # This will be made extensible so it can replace current domain specific check logic
8
- class TypeChecks
9
- include RSpec::Matchers
10
-
11
- def initialize
12
- @map = {
13
- Object: be_a_kind_of(Hash),
14
- String: be_a_kind_of(String),
15
- Number: be_a_kind_of(Numeric),
16
- Integer: be_a_kind_of(Integer),
17
- Array: be_a_kind_of(Array),
18
- DateTime: be_a_kind_of(Time),
19
- Boolean: satisfy{|it| it == true || it == false }
20
- }
21
- end
22
-
23
- def for_type(type)
24
- @map[type.to_sym] || raise("Unsupported type #{type}")
25
- end
26
- end
27
-
28
- module TypeChecking
29
- def type_checks
30
- @type_check ||= TypeChecks.new
31
- end
32
-
33
- def type_check_for(type)
34
- type_checks.for_type(type)
35
- end
36
- end