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.
@@ -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