vigia 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,8 +3,12 @@ module Vigia
3
3
  class RestClient
4
4
  attr_accessor :code, :headers, :body
5
5
 
6
- def initialize(http_options)
7
- @http_options = http_options
6
+ def initialize(http_client_options)
7
+ @http_client_options = http_client_options
8
+ end
9
+
10
+ def run
11
+ cache_or_perform_request
8
12
  end
9
13
 
10
14
  def run!
@@ -13,32 +17,50 @@ module Vigia
13
17
 
14
18
  private
15
19
 
16
- # :nocov:
20
+ # FIXME move cache to HttpClient::Base
21
+ def cache_or_perform_request
22
+ if cache.has_key?(request_key)
23
+ cache[request_key]
24
+ else
25
+ @@cache[request_key] = perform_request
26
+ end
27
+ end
28
+
29
+ def cache
30
+ @@cache ||= {}
31
+ end
32
+
33
+ def request_key
34
+ hash = @http_client_options.to_h.select do |k,v|
35
+ [ :headers, :method, :url, :payload ].include?(k)
36
+ end
37
+ Digest::MD5.hexdigest(hash.to_s)
38
+ end
39
+
17
40
  def parse_request(rest_client_result)
18
- {
19
- code: rest_client_result.code,
41
+ Vigia::HttpClient::ClientRequest.new(
42
+ code: rest_client_result.code,
20
43
  headers: rest_client_result.headers,
21
- body: rest_client_result.body
22
- }
44
+ body: rest_client_result.body
45
+ )
23
46
  end
24
47
 
25
48
  def error_request(exception)
26
- {
27
- code: exception.http_code, # Todo. Parse each exception
49
+ Vigia::HttpClient::ClientRequest.new(
50
+ code: exception.http_code, # Todo. Parse each exception
28
51
  headers: exception.response.headers,
29
- body: exception.response
30
- }
52
+ body: exception.http_body
53
+ )
31
54
  end
32
55
 
33
56
  def perform_request
34
57
  begin
35
- request = ::RestClient::Request.execute(@http_options)
58
+ request = ::RestClient::Request.execute(@http_client_options.to_h)
36
59
  parse_request(request)
37
60
  rescue ::RestClient::Exception => exception
38
61
  error_request(exception)
39
62
  end
40
63
  end
41
- # :nocov:
42
64
  end
43
65
  end
44
66
  end
@@ -1,22 +1,33 @@
1
1
  module Vigia
2
2
  class Parameters
3
3
 
4
- attr_reader :resource, :action
4
+ attr_reader :parameters
5
5
 
6
- def initialize(resource, action)
7
- @resource = resource
8
- @action = action
6
+ def initialize(parameters)
7
+ @parameters = parameters
8
+ validate
9
9
  end
10
10
 
11
11
  def to_hash
12
- all.each_with_object({}) do |parameter, collection|
13
- collection[parameter.name] = parameter.example_value
14
- collection
12
+ @parameters.each_with_object({}) do |parameter, hash|
13
+ hash.merge!(parameter[:name] => parameter[:value])
15
14
  end
16
15
  end
17
16
 
18
- def all
19
- resource.parameters.collection + action.parameters.collection
17
+ def validate
18
+ return if required_parameters.empty?
19
+
20
+ raise("Missing parameters #{ missing_parameters.join(',') }") unless missing_parameters.empty?
21
+ end
22
+
23
+ private
24
+
25
+ def required_parameters
26
+ parameters.select{ |k| k[:required] == true }
27
+ end
28
+
29
+ def missing_parameters
30
+ required_parameters.select{ |k| k[:value].nil? || k[:value].empty? }.map{ |k| k[:name] }
20
31
  end
21
32
  end
22
33
  end
data/lib/vigia/rspec.rb CHANGED
@@ -1,15 +1,13 @@
1
1
  module Vigia
2
2
  class Rspec
3
3
  class << self
4
- def apib
5
- Vigia.config.blueprint.apib
4
+ def adapter
5
+ @@adapter
6
6
  end
7
7
 
8
8
  def include_shared_folders
9
9
  require_custom_examples
10
10
  require "#{ __dir__ }/spec/support/utils"
11
- require "#{ __dir__ }/spec/support/shared_examples/skip_example"
12
- require "#{ __dir__ }/spec/support/shared_examples/apib_example"
13
11
  end
14
12
 
15
13
  private
@@ -22,26 +20,45 @@ module Vigia
22
20
  end
23
21
  end
24
22
 
25
- # Run `Vigia.spec_folder` spec file
26
23
  def run!
27
- with_options do
24
+ with_rspec_options do
28
25
  RSpec::Core::Runner::run(
29
- [ Vigia.spec_folder ], $stderr, $stdout)
26
+ [ Vigia.spec_folder ], Vigia.config.stderr, Vigia.config.stdout)
30
27
  end
31
28
  end
32
29
 
33
- private
30
+ def start_tests(rspec)
31
+ with_rspec_options do
32
+ set_global_memoizers(rspec)
33
+
34
+ Vigia::Sail::Group.setup_and_run_primary(rspec)
35
+ end
36
+ end
34
37
 
35
- # ToDo: Do we need rspec config?
38
+ def set_global_memoizers(rspec)
39
+ instance = adapter
40
+ rspec.let(:client) { Vigia.config.http_client_class.new(http_client_options) }
41
+ rspec.let(:result) { client.run }
42
+ rspec.let(:result!) { client.run! }
43
+ rspec.let(:adapter) { instance }
44
+ end
45
+
46
+ def adapter
47
+ @@adapter ||= Vigia.config.adapter.instance
48
+ end
36
49
 
37
- def with_options &block
50
+ private
51
+
52
+ def with_rspec_options
38
53
  RSpec.configure do |config|
39
- config.before(:each) do |example|
40
- end
41
- config.before(:suite) do
42
- end
54
+ configure_vigia_rspec(config)
43
55
  end
44
56
  yield
45
57
  end
58
+
59
+ def configure_vigia_rspec(rspec_config)
60
+ return unless Vigia.config.rspec_config_block.respond_to?(:call)
61
+ Vigia.config.rspec_config_block.call(rspec_config)
62
+ end
46
63
  end
47
64
  end
@@ -0,0 +1,67 @@
1
+ module Vigia
2
+ module Sail
3
+ class Context < RSpecObject
4
+
5
+ def run
6
+ instance = self
7
+ rspec.context instance.to_s do
8
+ instance.with_hooks(self) do
9
+ let(:http_client_options) { instance.set_http_client_options(self) }
10
+ let(:expectations) { instance.set_expectations(self) }
11
+
12
+ instance.run_examples(self)
13
+ instance.run_shared_examples(self)
14
+ end
15
+ end
16
+ end
17
+
18
+ def set_http_client_options(in_let_context)
19
+ Vigia::HttpClient::Options.build(self, in_let_context)
20
+ end
21
+
22
+ def set_expectations(in_let_context)
23
+ Vigia::HttpClient::ExpectedRequest.new.tap do |instance|
24
+ expectations.each do |name|
25
+ option_object = options[:expectations][name]
26
+ instance[name] = contextual_object(object: option_object, context: in_let_context)
27
+ end
28
+ instance
29
+ end
30
+ end
31
+
32
+ def run_examples(in_context)
33
+ Vigia::Sail::Example.run_in_context(self, in_context)
34
+ end
35
+
36
+ def run_shared_examples(in_context)
37
+ if custom_examples.any?
38
+ custom_examples.each do |example_name|
39
+ in_context.include_examples example_name
40
+ end
41
+ end
42
+ end
43
+
44
+ def to_s
45
+ "context #{ name }"
46
+ end
47
+
48
+ private
49
+
50
+ def custom_examples
51
+ Vigia.config.custom_examples.each_with_object([]) do |custom_example, collection|
52
+ collection << custom_example[:name] if custom_example[:filter] == :all
53
+ collection
54
+ end
55
+ end
56
+
57
+ def expectations
58
+ (options[:expectations] || {}).keys & default_expectations
59
+ end
60
+
61
+ def default_expectations
62
+ [ :code, :headers, :body ]
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,65 @@
1
+ module Vigia
2
+ module Sail
3
+ class Example < Vigia::Sail::RSpecObject
4
+ class << self
5
+ def register(name, options)
6
+ @collection = {} if collection.nil?
7
+ @collection.merge!(name => options)
8
+ end
9
+
10
+ def run_in_context(context, rspec_context)
11
+ @collection.each do |name, options|
12
+ setup_and_run(name, rspec_context) if example_contexts_include?(context, options[:contexts])
13
+ end
14
+ end
15
+
16
+ def example_contexts_include?(context, enabled_contexts)
17
+ [ *(enabled_contexts || :default) ].include?(context.name)
18
+ end
19
+ end
20
+
21
+ def run
22
+ instance = self
23
+ rspec.it instance do
24
+ skip if instance.skip?(self) || (respond_to?(:skip?) and send(:skip?))
25
+ skip('__vigia__') if instance.disabled?(self) || (respond_to?(:disabled?) and send(:disabled?))
26
+
27
+ instance_exec(&instance.expectation)
28
+ end
29
+ end
30
+
31
+ def expectation
32
+ must_be_a_block(options[:expectation], "Example `#{ name }` expectation must be a block")
33
+ end
34
+
35
+ def to_s
36
+ return contextual_object(option_name: :description) if options[:description]
37
+
38
+ name.to_s
39
+ end
40
+
41
+ def skip?(in_context)
42
+ return false unless options[:skip_if]
43
+ contextual_object(option_name: :skip_if, context: in_context)
44
+ end
45
+
46
+ def disabled?(in_context)
47
+ return false unless options[:disable_if]
48
+ contextual_object(option_name: :disable_if, context: in_context)
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+ Vigia::Sail::Example.register(
56
+ :code_match,
57
+ description: 'has the expected HTTP code',
58
+ expectation: -> { expect(result.code).to be(expectations.code) }
59
+ )
60
+
61
+ Vigia::Sail::Example.register(
62
+ :include_headers,
63
+ description: 'includes the expected headers',
64
+ expectation: -> { expect(result[:headers]).to include(expectations[:headers]) }
65
+ )
@@ -0,0 +1,47 @@
1
+ module Vigia
2
+ module Sail
3
+ class Group < Vigia::Sail::RSpecObject
4
+ class << self
5
+ def setup_and_run_primary(rspec)
6
+ name, options = collection.select{ |k,v| v[:primary] == true }.first
7
+ instance = new(name, options, rspec)
8
+ instance.run
9
+ end
10
+ end
11
+
12
+ def run
13
+ described_objects.each_with_index do |object, index|
14
+ group_instance = create_group_instance(object, index)
15
+ rspec.describe group_instance do
16
+ let(group_instance.group.name) { group_instance.described_object }
17
+
18
+ group_instance.run(self)
19
+ end
20
+ end
21
+ end
22
+
23
+ def children
24
+ [ *options[:children] ] || []
25
+ end
26
+
27
+ def contexts
28
+ [ *options[:contexts] ] || []
29
+ end
30
+
31
+ def described_objects
32
+ contextual_object(option_name: :describes) || []
33
+ end
34
+
35
+ def create_group_instance(object, index)
36
+ instance_name = "#{ name }#{ index }"
37
+ Vigia::Sail::GroupInstance.new(instance_name, options, rspec).tap do |instance|
38
+ instance.define_singleton_method("#{ name }", -> { object })
39
+ instance.parent_object = rspec.described_class.described_object unless options[:primary]
40
+ instance.described_object = object
41
+ instance.description = (options[:description] || object.to_s)
42
+ instance.group = self
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,24 @@
1
+ module Vigia
2
+ module Sail
3
+ class GroupInstance < Vigia::Sail::RSpecObject
4
+
5
+ attr_accessor :parent_object, :described_object, :group, :description
6
+
7
+ def run(rspec_context)
8
+ with_hooks(rspec_context) do
9
+ group.children.each do |group_name|
10
+ Vigia::Sail::Group.setup_and_run(group_name, rspec_context)
11
+ end
12
+
13
+ group.contexts.each do |context_name|
14
+ Vigia::Sail::Context.setup_and_run(context_name, rspec_context)
15
+ end
16
+ end
17
+ end
18
+
19
+ def to_s
20
+ description.respond_to?(:call) ? instance_exec(&description) : description
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,73 @@
1
+ module Vigia
2
+ module Sail
3
+ class RSpecObject
4
+ class << self
5
+ attr_accessor :collection
6
+
7
+ def setup_and_run(name, rspec)
8
+ name, options = collection.select{ |k,v| k == name }.first
9
+ instance = new(name, options, rspec)
10
+ instance.run
11
+ end
12
+ end
13
+
14
+ attr_reader :name, :options, :rspec
15
+
16
+ def initialize(name, options, rspec)
17
+ @name = name
18
+ @options = options
19
+ @rspec = rspec
20
+
21
+ end
22
+
23
+ def execute_hook(filter_name, rspec_context)
24
+ hooks_for_object(filter_name).each do |hook|
25
+ rspec_context.instance_exec(&hook)
26
+ end
27
+ end
28
+
29
+ def hooks_for_object(filter_name)
30
+ config_hooks(filter_name) + object_hooks(filter_name)
31
+ end
32
+
33
+ def with_hooks(rspec_context)
34
+ execute_hook(:before, rspec_context)
35
+ yield
36
+ execute_hook(:after, rspec_context)
37
+ end
38
+
39
+ def contextual_object(option_name: nil, object: nil, context: nil)
40
+ context ||= rspec.described_class
41
+ object ||= options[option_name]
42
+ case object
43
+ when Symbol
44
+ context.adapter.send(object)
45
+ when Proc
46
+ context.instance_exec(&object)
47
+ else
48
+ object
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def must_be_a_block(block, error_message)
55
+ return block if block.respond_to?(:call)
56
+ raise error_message
57
+ end
58
+
59
+ def object_hooks(filter_name)
60
+ option_name = "#{ filter_name }_#{ self.class.name.split('::').last.downcase }".to_sym
61
+ [ *options[option_name] ].compact
62
+
63
+ end
64
+
65
+ def config_hooks(filter_name)
66
+ Vigia.config.hooks.each_with_object([]) do |hook, collection|
67
+ next unless self.is_a?(hook[:rspec_class]) and filter_name == hook[:filter]
68
+ collection << hook[:block]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -3,29 +3,5 @@
3
3
  describe Vigia::Rspec do
4
4
 
5
5
  described_class.include_shared_folders
6
-
7
- described_class.apib.resource_groups.each do |resource_group|
8
- describe description_for(resource_group) do
9
- resource_group.resources.each do |resource|
10
- describe description_for(resource) do
11
- resource.actions.each do |action|
12
- describe description_for(action) do
13
- action.examples.each do |apib_example|
14
- runner_example = Vigia::Example.new(
15
- resource: resource,
16
- action: action,
17
- apib_example: apib_example)
18
-
19
- if runner_example.skip?
20
- include_examples 'skip example', runner_example
21
- else
22
- include_examples 'apib example', runner_example
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
30
- end
6
+ described_class.new.start_tests(self)
31
7
  end
@@ -1,20 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- def description_for object
4
- case object
5
- when RedSnow::ResourceGroup
6
- "Resource Group: #{ object.name }"
7
- when RedSnow::Resource
8
- "Resource: #{ object.name } (#{ object.uri_template })"
9
- when RedSnow::Action
10
- "Action: #{ object.name } (#{ object.method })"
11
- when RedSnow::Payload
12
- "Response: #{ object.name }"
13
- end
14
- end
15
-
16
3
  def format_error(result, expectation)
17
- <<-FORMATTED_ERROR
4
+ <<-FORMATTED_ERROR
18
5
 
19
6
  Expected:
20
7
 
data/lib/vigia/url.rb CHANGED
@@ -8,41 +8,25 @@ module Vigia
8
8
  # ApiBluprint uses RFC6570 in all its resource's uri_templates
9
9
  # https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#1-uri-templates
10
10
  def initialize(apib_uri_template)
11
- @uri_template = ::URITemplate.new(:rfc6570, apib_uri_template)
11
+ @uri_template = Addressable::Template.new(apib_uri_template)
12
12
  end
13
13
 
14
14
  def expand(parameters)
15
- validate(parameters) # if Vigia.config.validate_url_parameters?
16
-
17
- absolute_url uri_template.expand(parameters.to_hash)
15
+ absolute_url uri_template.expand(valid_parameters(parameters).to_hash)
18
16
  end
19
17
 
20
18
  def absolute_url path
21
19
  URI.join(host, path).to_s
22
20
  end
23
21
 
24
- def validate(parameters)
25
- return if required_template_parameters.empty?
26
-
27
- missing_parameters = required_template_parameters - valid_paremeters(parameters)
28
-
29
- raise("Uri template #{ @uri_template } needs parameter/s #{ missing_parameters.join(',') }") unless missing_parameters.empty?
30
- end
31
-
32
22
  def host
33
23
  Vigia.config.host
34
24
  end
35
25
 
36
26
  private
37
27
 
38
- def valid_paremeters(parameters)
39
- parameters.to_hash.delete_if{|_,v| v.nil? || v.empty? }.keys
40
- end
41
-
42
- def required_template_parameters
43
- uri_template.tokens.select{
44
- |token| token.is_a?(URITemplate::RFC6570::Expression::Basic)
45
- }.map(&:variables).flatten
28
+ def valid_parameters(parameters)
29
+ Vigia::Parameters.new(parameters)
46
30
  end
47
31
  end
48
32
  end
data/lib/vigia/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Vigia
4
- VERSION = "0.0.9"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/vigia.rb CHANGED
@@ -3,15 +3,21 @@
3
3
  require 'redsnow'
4
4
  require 'rspec'
5
5
  require 'rest_client'
6
- require 'uri_template'
6
+ require 'addressable/template'
7
7
 
8
- require_relative 'vigia/blueprint'
8
+ require_relative 'vigia/adapter'
9
+ require_relative 'vigia/adapters/blueprint'
9
10
  require_relative 'vigia/config'
10
- require_relative 'vigia/example'
11
- require_relative 'vigia/headers'
11
+ require_relative 'vigia/http_client/options'
12
12
  require_relative 'vigia/http_client/rest_client'
13
+ require_relative 'vigia/http_client/requests'
13
14
  require_relative 'vigia/parameters'
14
15
  require_relative 'vigia/rspec'
16
+ require_relative 'vigia/sail/rspec_object'
17
+ require_relative 'vigia/sail/example'
18
+ require_relative 'vigia/sail/context'
19
+ require_relative 'vigia/sail/group'
20
+ require_relative 'vigia/sail/group_instance'
15
21
  require_relative 'vigia/url'
16
22
  require_relative 'vigia/version'
17
23
 
@@ -23,7 +29,7 @@ module Vigia
23
29
  def configure
24
30
  @config = Vigia::Config.new
25
31
  yield @config
26
- @config.validate!
32
+ @config.apply
27
33
  end
28
34
  def spec_folder
29
35
  File.join(__dir__, 'vigia', 'spec')