setsuzoku 0.10.1
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 +7 -0
- data/.gitattributes +2 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/setsuzoku.rb +37 -0
- data/lib/setsuzoku/api_response.rb +11 -0
- data/lib/setsuzoku/api_strategy.rb +124 -0
- data/lib/setsuzoku/auth_strategy.rb +72 -0
- data/lib/setsuzoku/credential.rb +37 -0
- data/lib/setsuzoku/exception.rb +18 -0
- data/lib/setsuzoku/external_api_handler.rb +19 -0
- data/lib/setsuzoku/pluggable.rb +87 -0
- data/lib/setsuzoku/plugin.rb +128 -0
- data/lib/setsuzoku/rspec.rb +2 -0
- data/lib/setsuzoku/rspec/dynamic_spec_helper.rb +281 -0
- data/lib/setsuzoku/service.rb +70 -0
- data/lib/setsuzoku/service/web_service.rb +21 -0
- data/lib/setsuzoku/service/web_service/api_strategies/rest_api_request.rb +17 -0
- data/lib/setsuzoku/service/web_service/api_strategies/rest_strategy.rb +155 -0
- data/lib/setsuzoku/service/web_service/api_strategy.rb +169 -0
- data/lib/setsuzoku/service/web_service/auth_strategies/basic_auth_strategy.rb +50 -0
- data/lib/setsuzoku/service/web_service/auth_strategies/o_auth_strategy.rb +173 -0
- data/lib/setsuzoku/service/web_service/auth_strategy.rb +48 -0
- data/lib/setsuzoku/service/web_service/credentials/basic_auth_credential.rb +52 -0
- data/lib/setsuzoku/service/web_service/credentials/o_auth_credential.rb +83 -0
- data/lib/setsuzoku/service/web_service/service.rb +31 -0
- data/lib/setsuzoku/utilities.rb +7 -0
- data/lib/setsuzoku/version.rb +6 -0
- data/setsuzoku.gemspec +50 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/activesupport.rbi +1125 -0
- data/sorbet/rbi/gems/addressable.rbi +199 -0
- data/sorbet/rbi/gems/concurrent-ruby.rbi +1586 -0
- data/sorbet/rbi/gems/crack.rbi +62 -0
- data/sorbet/rbi/gems/faraday.rbi +615 -0
- data/sorbet/rbi/gems/hashdiff.rbi +66 -0
- data/sorbet/rbi/gems/httparty.rbi +401 -0
- data/sorbet/rbi/gems/i18n.rbi +133 -0
- data/sorbet/rbi/gems/mime-types-data.rbi +17 -0
- data/sorbet/rbi/gems/mime-types.rbi +218 -0
- data/sorbet/rbi/gems/multi_xml.rbi +35 -0
- data/sorbet/rbi/gems/multipart-post.rbi +53 -0
- data/sorbet/rbi/gems/nokogiri.rbi +1011 -0
- data/sorbet/rbi/gems/public_suffix.rbi +104 -0
- data/sorbet/rbi/gems/rake.rbi +646 -0
- data/sorbet/rbi/gems/rspec-core.rbi +1893 -0
- data/sorbet/rbi/gems/rspec-expectations.rbi +1123 -0
- data/sorbet/rbi/gems/rspec-mocks.rbi +1090 -0
- data/sorbet/rbi/gems/rspec-support.rbi +280 -0
- data/sorbet/rbi/gems/rspec.rbi +15 -0
- data/sorbet/rbi/gems/safe_yaml.rbi +124 -0
- data/sorbet/rbi/gems/thread_safe.rbi +82 -0
- data/sorbet/rbi/gems/tzinfo.rbi +406 -0
- data/sorbet/rbi/gems/webmock.rbi +532 -0
- data/sorbet/rbi/hidden-definitions/hidden.rbi +13722 -0
- data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +1431 -0
- data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
- data/sorbet/rbi/sorbet-typed/lib/httparty/all/httparty.rbi +427 -0
- data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +108 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
- data/sorbet/rbi/todo.rbi +11 -0
- metadata +255 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Setsuzoku
|
5
|
+
# The API Authentication Interface definition.
|
6
|
+
# Any AuthStrategy that implements this interface must implement all abstract methods defined by AuthStrategy.
|
7
|
+
#
|
8
|
+
# Defines all necessary methods for handling authentication for any authentication strategy.
|
9
|
+
module AuthStrategy
|
10
|
+
|
11
|
+
extend Forwardable
|
12
|
+
extend T::Sig
|
13
|
+
extend T::Helpers
|
14
|
+
abstract!
|
15
|
+
|
16
|
+
attr_accessor :service
|
17
|
+
attr_accessor :credential
|
18
|
+
def_delegators :@service, :plugin, :api_strategy, :external_api_handler
|
19
|
+
|
20
|
+
# Initialize the auth_strategy and provide reference to service.
|
21
|
+
#
|
22
|
+
# @param service [Service] the new instance of service with its correct strategies.
|
23
|
+
#
|
24
|
+
# @return [AuthStrategy] the new instance of auth_strategy
|
25
|
+
sig(:final) do
|
26
|
+
params(
|
27
|
+
service: T.any(
|
28
|
+
Setsuzoku::Service::WebService::Service,
|
29
|
+
T.untyped
|
30
|
+
),
|
31
|
+
args: T.untyped
|
32
|
+
).returns(T.any(
|
33
|
+
Setsuzoku::Service::WebService::AuthStrategies::BasicAuthStrategy,
|
34
|
+
Setsuzoku::Service::WebService::AuthStrategies::OAuthStrategy,
|
35
|
+
T.untyped
|
36
|
+
))
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(service:, **args)
|
40
|
+
self.service = service
|
41
|
+
credential = args[:credential]
|
42
|
+
self.credential = if self.plugin.registered_instance
|
43
|
+
credential
|
44
|
+
else
|
45
|
+
self.class.credential_class.stub_credential
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# The getter method for a credential should retrieve dynamically if it's a proc.
|
51
|
+
# We cannot assign this at initialize time, as it may not yet exist on the instance.
|
52
|
+
#
|
53
|
+
# @return [Credential] the credential to use for the current requests.
|
54
|
+
def credential
|
55
|
+
self.plugin.get_registered_instance_val(@credential)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Check if a credential is valid.
|
59
|
+
# Additionally it should revalidate if invalid.
|
60
|
+
#
|
61
|
+
# @return [Boolean] true if the credential is valid.
|
62
|
+
sig { abstract.returns(T::Boolean) }
|
63
|
+
def auth_credential_valid?; end
|
64
|
+
|
65
|
+
# Authorize a credential for a specific auth_strategy.
|
66
|
+
# It should also set the credential attributes and save if appropriate.
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
sig { abstract.params(args: T.untyped).void }
|
70
|
+
def new_credential!(**args); end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
# typed: ignore
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
module Setsuzoku
|
6
|
+
module Credential
|
7
|
+
extend T::Sig
|
8
|
+
extend T::Helpers
|
9
|
+
interface!
|
10
|
+
|
11
|
+
# Create a stub implementation of the credential for testing purposes
|
12
|
+
#
|
13
|
+
# @return [Struct] a struct that implements stubbed version of a credential's required methods.
|
14
|
+
sig { abstract.returns(Struct) }
|
15
|
+
def self.stub_credential; end
|
16
|
+
|
17
|
+
# A settings object for the credential.
|
18
|
+
#
|
19
|
+
# @return [Hash] the settings for the credential.
|
20
|
+
sig { abstract.returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
|
21
|
+
def settings; end
|
22
|
+
|
23
|
+
# Setter for the settings object for the credential.
|
24
|
+
#
|
25
|
+
# @param val [Hash] a settings object to set.
|
26
|
+
#
|
27
|
+
# @return [Hash] the settings for the credential.
|
28
|
+
sig do
|
29
|
+
abstract
|
30
|
+
.params(val: T.nilable(T::Hash[T.untyped, T.untyped]))
|
31
|
+
.returns(T.nilable(T::Hash[T.untyped, T.untyped]))
|
32
|
+
end
|
33
|
+
def settings=(val); end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
module Setsuzoku
|
4
|
+
module Exception
|
5
|
+
class InvalidAuthCredentials < StandardError
|
6
|
+
def initialize(msg='The authentication credentials for this request are invalid.')
|
7
|
+
super(msg)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UndefinedRequiredMethod < StandardError
|
12
|
+
def initialize(registering_instance:, plugin_class:, method_name:)
|
13
|
+
msg = "#{registering_instance.name} attempted to register #{plugin_class.name} but did not define the required instance method: #{method_name}"
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Setsuzoku
|
5
|
+
# The base definition for the exception handling class.
|
6
|
+
# This can be overridden by application configuration.
|
7
|
+
class ExternalApiHandler
|
8
|
+
def call_external_api_wrapper(**args)
|
9
|
+
puts('Setsuzoku API call pending')
|
10
|
+
response = yield
|
11
|
+
puts("Setsuzoku API call complete. Success status: #{response[:success]}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def call_external_api_exception(**args)
|
15
|
+
puts(args[:exception].backtrace.join("\n")) if args[:exception]
|
16
|
+
puts("call_external_api failed with: #{args.inspect}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Setsuzoku
|
5
|
+
# The module to include into a class to give it plugin functionality.
|
6
|
+
module Pluggable
|
7
|
+
extend Forwardable
|
8
|
+
extend T::Sig
|
9
|
+
extend T::Helpers
|
10
|
+
abstract!
|
11
|
+
|
12
|
+
attr_accessor :plugin
|
13
|
+
def_delegators :@plugin, :plugin_service, :plugin_request_class
|
14
|
+
|
15
|
+
def self.included(klass)
|
16
|
+
klass.extend(ClassMethods)
|
17
|
+
klass.class_eval do
|
18
|
+
if klass.respond_to?(:after_initialize)
|
19
|
+
after_initialize :assign_plugin
|
20
|
+
else
|
21
|
+
# Initialize the pluggable instance and associate its plugin.
|
22
|
+
#
|
23
|
+
# @param _args [Array] array of args passed into new. #unused
|
24
|
+
# @param _block [Proc] the block passed into new. #unused
|
25
|
+
#
|
26
|
+
# @return [Class] the instance of the class that is pluggable.
|
27
|
+
def initialize(*_args, &_block)
|
28
|
+
super
|
29
|
+
self.assign_plugin
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
extend T::Sig
|
38
|
+
|
39
|
+
def plugin_context
|
40
|
+
@plugin_context
|
41
|
+
end
|
42
|
+
def plugin_context=(val)
|
43
|
+
@plugin_context = val
|
44
|
+
end
|
45
|
+
def plugin_class
|
46
|
+
@plugin_class
|
47
|
+
end
|
48
|
+
def plugin_class=(val)
|
49
|
+
@plugin_class = val
|
50
|
+
end
|
51
|
+
|
52
|
+
def default_options
|
53
|
+
{
|
54
|
+
plugin_class: self
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Upon class load (or whenever you call this) register the core configuration
|
59
|
+
# for the pluggable class.
|
60
|
+
#
|
61
|
+
# param options [Any] a list of keyword options to be passed in to customize registration.
|
62
|
+
#
|
63
|
+
# @return [Void]
|
64
|
+
sig(:final) { params(options: T.untyped).void }
|
65
|
+
def register_plugin(**options)
|
66
|
+
options[:plugin_class].config_context[:required_instance_methods].each do |req_method|
|
67
|
+
|
68
|
+
unless options[:required_instance_methods].key?(req_method)
|
69
|
+
raise Setsuzoku::Exception::UndefinedRequiredMethod.new(registering_instance: self, plugin_class: options[:plugin_class], method_name: req_method)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
self.plugin_context = self.default_options.merge(options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def assign_plugin(*_args, &block)
|
78
|
+
self.plugin = self.class.plugin_context[:plugin_class].new(registering_instance: self, **self.class.plugin_context.except(:plugin_class))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Define the Plugin that this class should use for its plugin interface.
|
82
|
+
#
|
83
|
+
# @return [Class] any class in Setsuzoku::Plugin
|
84
|
+
sig { abstract.returns(T.untyped) }
|
85
|
+
def plugin_class; end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Setsuzoku
|
5
|
+
# The base definition for a plugin.
|
6
|
+
# A plugin is a unification of an ApiStrategy and an AuthStrategy.
|
7
|
+
# It allows each Strategy to manage its various jobs of sending/receiving requests, and managing authentication/connections.
|
8
|
+
# However it acts as the director that allows these 2 to interact with one another.
|
9
|
+
module Plugin
|
10
|
+
autoload :Mobile, 'setsuzoku/plugin/mobile'
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
extend T::Sig
|
14
|
+
extend T::Helpers
|
15
|
+
abstract!
|
16
|
+
|
17
|
+
attr_accessor :name
|
18
|
+
attr_accessor :registered_instance
|
19
|
+
attr_accessor :service
|
20
|
+
attr_accessor :config_context
|
21
|
+
def_delegators :@service, :auth_strategy, :api_strategy, :exception_handler, :call_external_api, :request_class, :new_credential!
|
22
|
+
|
23
|
+
alias :plugin_service :service
|
24
|
+
alias :plugin_request_class :request_class
|
25
|
+
|
26
|
+
AVAILABLE_SERVICES = {
|
27
|
+
web_service: Setsuzoku::Service::WebService::Service
|
28
|
+
}
|
29
|
+
|
30
|
+
def self.included(klass)
|
31
|
+
klass.extend(ClassMethods)
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def config_context
|
36
|
+
@config_context
|
37
|
+
end
|
38
|
+
|
39
|
+
def config_context=(val)
|
40
|
+
@config_context = val
|
41
|
+
end
|
42
|
+
# Register the service for a plugin.
|
43
|
+
# This will collect configuration level data to use when a plugin is registered.
|
44
|
+
# It will also impose interface requirements on the registering class.
|
45
|
+
#
|
46
|
+
# @param name [String] the name of the plugin.
|
47
|
+
# @param options [Any] list of keyword options to customize service registration.
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
def register(name:, service:, **options)
|
51
|
+
context = {
|
52
|
+
name: name,
|
53
|
+
service: service
|
54
|
+
}
|
55
|
+
|
56
|
+
# @options are any additional options you want available inside the plugin
|
57
|
+
|
58
|
+
options.each do |key, val|
|
59
|
+
context[key] = val || self.with_options[key]
|
60
|
+
end
|
61
|
+
|
62
|
+
required_instance_methods = options[:required_instance_methods] || []
|
63
|
+
service[:strategies].each do |type, strategy|
|
64
|
+
type = type.to_s.split('_strategy').first.to_sym
|
65
|
+
strategy = strategy[:strategy]
|
66
|
+
strategy_klass = AVAILABLE_SERVICES[service[:type]].available_strategies[type][strategy]
|
67
|
+
required_instance_methods += strategy_klass.required_instance_methods
|
68
|
+
include strategy_klass.superclass::InterfaceMethods
|
69
|
+
end
|
70
|
+
|
71
|
+
context[:required_instance_methods] = required_instance_methods
|
72
|
+
|
73
|
+
self.config_context = context
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Initialize the plugin.
|
78
|
+
#
|
79
|
+
# @param registering_instance [Any] an instance of a class that needs to register with a plugin.
|
80
|
+
#
|
81
|
+
# @return the new registered instance of a plugin.
|
82
|
+
sig(:final) { params(options: T.untyped).returns(T.untyped) }
|
83
|
+
def initialize(**options)
|
84
|
+
context = self.class.config_context || { name: 'Default plugin', service: {} }
|
85
|
+
self.name = context[:name]
|
86
|
+
service_config = context[:service].except(:type).merge({ plugin: self, credential: options[:credential] })
|
87
|
+
self.registered_instance = options[:registering_instance]
|
88
|
+
if context[:service] && context[:service][:type]
|
89
|
+
service = AVAILABLE_SERVICES[context[:service][:type]]
|
90
|
+
self.service = service.new(service_config)
|
91
|
+
end
|
92
|
+
self.config_context = context.merge(options.except(:registering_instance))
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# This method allows a safe way to access the registered_instance's methods or specify a static value
|
97
|
+
# This allows allows plugins to be tested in isolation, e.g. independent of a class that uses them.
|
98
|
+
#
|
99
|
+
# @param method_name [Symbol] the name of the method to try or to stub.
|
100
|
+
# @param args [Array] any additional args that need to be passed to the dynamic method.
|
101
|
+
#
|
102
|
+
# @return [Any] any value that the method could return.
|
103
|
+
sig(:final) { params(method_name: Symbol, args: T.untyped).returns(T.untyped) }
|
104
|
+
def get_from_registered_instance_method(method_name, *args)
|
105
|
+
if self.registered_instance
|
106
|
+
#either get the value if its defined generically in the
|
107
|
+
val = self.config_context[:required_instance_methods][method_name.to_sym]
|
108
|
+
self.get_registered_instance_val(val, *args)
|
109
|
+
else
|
110
|
+
#TODO: this needs to return any data type somehow...the plugin might need to stub this, as it stubs tests as well...
|
111
|
+
# this seems like a reasonable approach...
|
112
|
+
"stubbed_#{method_name}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Convenience method for performing instance_exec on the registered_instance.
|
117
|
+
# This is used for calling procs that are defined in the application's registration procs.
|
118
|
+
#
|
119
|
+
# @param val [Any] the value or a proc to instance_exec to get the value.
|
120
|
+
# @param args [Array] a list of args to be used for the proc.
|
121
|
+
#
|
122
|
+
# @return [Any] returns the value of the proc.
|
123
|
+
sig(:final) { params(val: T.untyped, args: T.untyped).returns(T.untyped) }
|
124
|
+
def get_registered_instance_val(val, *args)
|
125
|
+
val.is_a?(Proc) ? self.registered_instance.instance_exec(*args, &val) : val
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
require 'active_support/core_ext/class/subclasses'
|
4
|
+
require 'active_support/inflector/methods'
|
5
|
+
module Setsuzoku
|
6
|
+
module DynamicSpecHelper
|
7
|
+
|
8
|
+
### EXTERNAL PLUGIN SPEC HELPERS ###
|
9
|
+
#
|
10
|
+
# These specs are designed to test and ensure that a plugin's implementations uphold their contract.
|
11
|
+
# Additionally they ensure that a plugin's blackbox stubbing also upholds their contract.
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# This will register plugin specs for a base plugin class that defines its interfaces.
|
15
|
+
#
|
16
|
+
# It will register specs to ensure the base plugin's black box stubs are correct.
|
17
|
+
#
|
18
|
+
# It will also register specs for all implementations of the plugin to ensure the implementations are valid.
|
19
|
+
# (This is similar to register_plugin_tests_and_stubs)
|
20
|
+
def register_all_plugin_tests_and_stubs(plugin_class: self.described_class, subclasses_to_register: plugin_class.subclasses, registering_instance: nil, stub_directory: nil)
|
21
|
+
context "*Dynamic Specs* for #{plugin_class.name}: Stub Definitions" do
|
22
|
+
let(:plugin){ plugin_class.new }
|
23
|
+
plugin_class.spec_definitions.each do |action_name, spec|
|
24
|
+
define_stub_plugin_method(plugin_class, action_name, spec)
|
25
|
+
it(spec[:name], action_name: action_name, &spec[:spec])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
perform_register(plugin_class, registering_instance: registering_instance, subclasses_to_register: subclasses_to_register, stub_directory: stub_directory)
|
30
|
+
end
|
31
|
+
#
|
32
|
+
# This will register plugin specs for a single plugin class.
|
33
|
+
# The specs will test the plugin to ensure its implementations are valid.
|
34
|
+
def register_plugin_tests_and_stubs(plugin_class, registering_instance: nil, stub_directory: nil)
|
35
|
+
perform_register(plugin_class, registering_instance: registering_instance, stub_directory: stub_directory)
|
36
|
+
end
|
37
|
+
#
|
38
|
+
### END EXTERNAL PLUGIN SPEC HELPERS ###
|
39
|
+
|
40
|
+
|
41
|
+
### APPLICATION SPEC HELPERS ###
|
42
|
+
#
|
43
|
+
# These stubs completely black box the plugin's functionality and returns a stubbed response
|
44
|
+
# without invoking the plugin's method.
|
45
|
+
#
|
46
|
+
# Stub all plugin interface methods at a context level for an application class that registers a plugin.
|
47
|
+
# This also defines a before each to execute the stubs.
|
48
|
+
#
|
49
|
+
# @param registering_class [Array/Class] the application's class/es that register a plugin and need methods stubbed.
|
50
|
+
#
|
51
|
+
# @return void
|
52
|
+
def stub_all_plugin_methods(registering_class = self.described_class)
|
53
|
+
registering_class = [registering_class] unless registering_class.is_a?(Array)
|
54
|
+
registering_class.each do |klass|
|
55
|
+
plugin_class = klass.plugin_context[:plugin_class]
|
56
|
+
plugin_class.spec_definitions.each do |action_name, spec|
|
57
|
+
define_stub_plugin_method(plugin_class, action_name, spec, true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
#
|
62
|
+
# Stub all plugin interface methods for an application class that registers a plugin for a single spec.
|
63
|
+
#
|
64
|
+
# @param registering_class [Array/Class] the application's class/es that register a plugin and need methods stubbed.
|
65
|
+
#
|
66
|
+
# @return void
|
67
|
+
def stub_plugin_methods_for_spec(registering_class = self.described_class)
|
68
|
+
plugin_class = registering_class.plugin_context[:plugin_class]
|
69
|
+
plugin_class.spec_definitions.each do |action_name, spec|
|
70
|
+
allow_any_instance_of(plugin_class).to receive(action_name).and_return(spec[:stub][:response])
|
71
|
+
# a plugin spec might have some dynamic data, e.g. certain methods are called with plugin specific args.
|
72
|
+
if spec[:stub][:dynamic_methods]
|
73
|
+
spec[:stub][:dynamic_methods].each do |meth, resp|
|
74
|
+
#how can we ignore the call count instead of just putting a high limit?
|
75
|
+
allow_any_instance_of(plugin_class).to receive(meth).and_return(resp)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
#
|
81
|
+
### END APPLICATION SPEC HELPERS ###
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def perform_register(plugin_class, register_tests = true, registering_instance: nil, subclasses_to_register: nil, stub_directory: nil)
|
86
|
+
#iterate over all api methods defined by the plugin and create a context and tests for each method
|
87
|
+
# in register_tests_and_get_stubs, and return the stub defs up to this method.
|
88
|
+
plugin_classes = subclasses_to_register || [plugin_class]
|
89
|
+
plugin_classes.each do |plugin_class|
|
90
|
+
p = plugin_class.new(registering_instance: registering_instance)
|
91
|
+
register_tests_and_get_stubs(p, register_tests).each do |method_name, stubs|
|
92
|
+
register_stub(p, method_name, stubs, registering_instance: registering_instance, stub_directory: stub_directory)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# This will iterate over every registered plugin defined in the including helper
|
98
|
+
# and generate formatted hashes of stub configs.
|
99
|
+
#
|
100
|
+
# register_stubs uses these config objects to define stubs dynamically
|
101
|
+
def register_tests_and_get_stubs(plugin, register_tests = true)
|
102
|
+
definitions = {}
|
103
|
+
specs = []
|
104
|
+
action_name_namespace = :"#{plugin.name.gsub(' ', '').underscore}"
|
105
|
+
stub_name = :"stub_#{action_name_namespace}"
|
106
|
+
definitions[stub_name] = { action_name_namespace => {} }
|
107
|
+
plugin.api_actions.each do |api_action|
|
108
|
+
action_name, options = api_action
|
109
|
+
plugin.api_strategy.current_action = action_name
|
110
|
+
request_props = plugin.api_strategy.get_request_properties(action_name: action_name, for_stub: true)
|
111
|
+
(definitions[stub_name][action_name_namespace][action_name] ||= []) << {
|
112
|
+
method: request_props[:request_method],
|
113
|
+
request_format: request_props[:request_format],
|
114
|
+
response_format: request_props[:response_format],
|
115
|
+
url: request_props[:formatted_full_url],
|
116
|
+
stub_data: request_props[:stub_data]
|
117
|
+
}
|
118
|
+
# include the generic interface specs for this plugin's action
|
119
|
+
specs << { spec: plugin.class.spec_definitions[action_name], action_name: action_name }
|
120
|
+
end
|
121
|
+
if register_tests
|
122
|
+
context "*Dynamic Specs* for #{plugin.class.name}: Interface Implementations" do
|
123
|
+
#we will instantiate the plugin, any registered_instance dependent methods/data must be stubbed by the plugin because of this!
|
124
|
+
let!(:plugin) { plugin }
|
125
|
+
|
126
|
+
before :each do
|
127
|
+
send(stub_name, self)
|
128
|
+
end
|
129
|
+
|
130
|
+
specs.each do |spec_def|
|
131
|
+
if spec_def[:spec] && spec_def[:spec][:spec]
|
132
|
+
it(spec_def[:spec][:name], &spec_def[:spec][:spec])
|
133
|
+
else
|
134
|
+
it "Undefined spec for #{spec_def[:action_name]}" do
|
135
|
+
raise NotImplemented, "Spec for action: #{spec_def[:action_name]} is not defined. Expected #{plugin.class} or its parent to define it."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
definitions
|
143
|
+
end
|
144
|
+
|
145
|
+
# This method will then iterate over stub defs and actually define the stubs here in the stub_defs array.
|
146
|
+
# It will then lastly collect all the stubs in stub_defs array and do call to define the stub.
|
147
|
+
def register_stub(plugin, method_name, stubs, registering_instance: nil, stub_directory: nil)
|
148
|
+
stub_defs = []
|
149
|
+
stubs.each do |provider, stub|
|
150
|
+
stub.each do |action_name, props|
|
151
|
+
props.each do |propz|
|
152
|
+
plugin.api_strategy.current_action = action_name
|
153
|
+
default_headers = plugin.api_strategy.request_options(propz[:request_format])[:headers] || { 'Authorization' => 'Bearer stubbed_token', 'Content-Type' => "application/#{propz[:request_format]}" }
|
154
|
+
if plugin.auth_strategy.is_a?(Setsuzoku::Service::WebService::AuthStrategies::BasicAuthStrategy)
|
155
|
+
basic_auth = plugin.api_strategy.request_options(propz[:request_format])[:basic_auth]
|
156
|
+
auth_header = { 'Authorization' => "Basic #{Base64.encode64("#{basic_auth[:username]}:#{basic_auth[:password]}").gsub("\n", '')}" }
|
157
|
+
default_headers.merge!(auth_header)
|
158
|
+
end
|
159
|
+
headers = propz[:headers] ? [propz[:headers]] : [default_headers]
|
160
|
+
stub_directory ||= "#{plugin.class.plugin_namespace}/#{provider}"
|
161
|
+
headers.each do |header|
|
162
|
+
header = {
|
163
|
+
'Accept'=>'*/*',
|
164
|
+
'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
|
165
|
+
'User-Agent'=>'Ruby'
|
166
|
+
}.merge(header.deep_stringify_keys)
|
167
|
+
|
168
|
+
if propz[:stub_data]
|
169
|
+
# we have specified specific stub data for this action
|
170
|
+
propz[:stub_data].each do |stub_datum|
|
171
|
+
url, body = get_url_and_body(plugin, propz, stub_directory, action_name, stub_datum[:url_params])
|
172
|
+
|
173
|
+
stub_defs << proc do
|
174
|
+
stub_request(propz[:method], url)
|
175
|
+
.with(
|
176
|
+
headers: header, body: body, query: stub_datum[:url_params]
|
177
|
+
)
|
178
|
+
.to_return(status: 200, headers: {}, body: File.read("#{Dir.pwd}/spec/support/setsuzoku/#{plugin.class.plugin_namespace}/#{provider}/#{action_name}.#{propz[:response_format]}"))
|
179
|
+
end
|
180
|
+
end
|
181
|
+
else
|
182
|
+
# this is a generic action so the stub uses the default structure
|
183
|
+
url, body = get_url_and_body(plugin, propz, stub_directory, action_name)
|
184
|
+
|
185
|
+
stub_defs << proc do
|
186
|
+
stub_request(propz[:method], url)
|
187
|
+
.with(
|
188
|
+
headers: header, body: body
|
189
|
+
)
|
190
|
+
.to_return(status: 200, headers: {}, body: File.read("#{Dir.pwd}/spec/support/setsuzoku/#{plugin.class.plugin_namespace}/#{provider}/#{action_name}.#{propz[:response_format]}"))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
#iterate over all the stub_requests collected above and call them to stub them.
|
199
|
+
define_method method_name do |spec_instance = self|
|
200
|
+
stub_defs.each do |stub_def|
|
201
|
+
instance_exec(&stub_def)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def define_stub_plugin_method(plugin_class, action_name, spec, always_stub = false)
|
207
|
+
stub_plugin_method_name = :"stub_plugin_method_#{action_name}"
|
208
|
+
define_method stub_plugin_method_name do
|
209
|
+
#TODO: let this use the appropriate rspec instance stub method
|
210
|
+
#TODO: Does it make sense to have the stub with the method? or should it be in a json file we parse?
|
211
|
+
plugin_class.any_instance.should_receive(action_name).and_return(spec[:stub][:response])
|
212
|
+
# a plugin spec might have some dynamic data, e.g. certain methods are called with plugin specific args.
|
213
|
+
if spec[:stub][:dynamic_methods]
|
214
|
+
spec[:stub][:dynamic_methods].each do |meth, resp|
|
215
|
+
#how can we ignore the call count instead of just putting a high limit?
|
216
|
+
plugin_class.any_instance.should_receive(meth).and_return(resp)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
if always_stub
|
221
|
+
before :each do
|
222
|
+
send(stub_plugin_method_name)
|
223
|
+
end
|
224
|
+
else
|
225
|
+
before :each, action_name: action_name do
|
226
|
+
send(stub_plugin_method_name)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_url_and_body(plugin, propz, stub_directory, action_name, url_params = nil)
|
232
|
+
body = nil
|
233
|
+
url = propz[:url]
|
234
|
+
format = propz[:request_format].to_s.include?('x-www-form') ? :json : propz[:request_format]
|
235
|
+
begin
|
236
|
+
body = File.read("#{Dir.pwd}/spec/support/setsuzoku/#{stub_directory}/#{action_name}_request.#{format}")
|
237
|
+
body.squish!
|
238
|
+
case format
|
239
|
+
when :xml
|
240
|
+
body.gsub!('> <', '><')
|
241
|
+
when :json
|
242
|
+
body.gsub!(' "', '"')
|
243
|
+
body.gsub!('" ', '"')
|
244
|
+
body.gsub!(': ', ':')
|
245
|
+
body.gsub!('{ ', '{')
|
246
|
+
body.gsub!(' }', '}')
|
247
|
+
end
|
248
|
+
|
249
|
+
# We will convert these to url params when making requests. need to replicate that.
|
250
|
+
|
251
|
+
|
252
|
+
req_params = case format
|
253
|
+
when :json
|
254
|
+
temp_body = JSON.parse(body)
|
255
|
+
if url_params
|
256
|
+
url_params.each{ |k,v| temp_body.delete(k.to_s) }
|
257
|
+
end
|
258
|
+
temp_body
|
259
|
+
else
|
260
|
+
# will we need to parse xml here?
|
261
|
+
req_params = {}
|
262
|
+
body
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
if url.match?(/.*\{\{.*\}\}/)
|
267
|
+
url, body = plugin.api_strategy.replace_dynamic_vars(full_url: url, req_params: req_params)
|
268
|
+
end
|
269
|
+
|
270
|
+
case format
|
271
|
+
when :json
|
272
|
+
#faraday sorts hash keys for some reason
|
273
|
+
body = JSON.parse(body).sort.to_h
|
274
|
+
end
|
275
|
+
[url, body]
|
276
|
+
rescue
|
277
|
+
[url, nil]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|