setsuzoku 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +7 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +82 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +93 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/lib/setsuzoku.rb +37 -0
  15. data/lib/setsuzoku/api_response.rb +11 -0
  16. data/lib/setsuzoku/api_strategy.rb +124 -0
  17. data/lib/setsuzoku/auth_strategy.rb +72 -0
  18. data/lib/setsuzoku/credential.rb +37 -0
  19. data/lib/setsuzoku/exception.rb +18 -0
  20. data/lib/setsuzoku/external_api_handler.rb +19 -0
  21. data/lib/setsuzoku/pluggable.rb +87 -0
  22. data/lib/setsuzoku/plugin.rb +128 -0
  23. data/lib/setsuzoku/rspec.rb +2 -0
  24. data/lib/setsuzoku/rspec/dynamic_spec_helper.rb +281 -0
  25. data/lib/setsuzoku/service.rb +70 -0
  26. data/lib/setsuzoku/service/web_service.rb +21 -0
  27. data/lib/setsuzoku/service/web_service/api_strategies/rest_api_request.rb +17 -0
  28. data/lib/setsuzoku/service/web_service/api_strategies/rest_strategy.rb +155 -0
  29. data/lib/setsuzoku/service/web_service/api_strategy.rb +169 -0
  30. data/lib/setsuzoku/service/web_service/auth_strategies/basic_auth_strategy.rb +50 -0
  31. data/lib/setsuzoku/service/web_service/auth_strategies/o_auth_strategy.rb +173 -0
  32. data/lib/setsuzoku/service/web_service/auth_strategy.rb +48 -0
  33. data/lib/setsuzoku/service/web_service/credentials/basic_auth_credential.rb +52 -0
  34. data/lib/setsuzoku/service/web_service/credentials/o_auth_credential.rb +83 -0
  35. data/lib/setsuzoku/service/web_service/service.rb +31 -0
  36. data/lib/setsuzoku/utilities.rb +7 -0
  37. data/lib/setsuzoku/version.rb +6 -0
  38. data/setsuzoku.gemspec +50 -0
  39. data/sorbet/config +2 -0
  40. data/sorbet/rbi/gems/activesupport.rbi +1125 -0
  41. data/sorbet/rbi/gems/addressable.rbi +199 -0
  42. data/sorbet/rbi/gems/concurrent-ruby.rbi +1586 -0
  43. data/sorbet/rbi/gems/crack.rbi +62 -0
  44. data/sorbet/rbi/gems/faraday.rbi +615 -0
  45. data/sorbet/rbi/gems/hashdiff.rbi +66 -0
  46. data/sorbet/rbi/gems/httparty.rbi +401 -0
  47. data/sorbet/rbi/gems/i18n.rbi +133 -0
  48. data/sorbet/rbi/gems/mime-types-data.rbi +17 -0
  49. data/sorbet/rbi/gems/mime-types.rbi +218 -0
  50. data/sorbet/rbi/gems/multi_xml.rbi +35 -0
  51. data/sorbet/rbi/gems/multipart-post.rbi +53 -0
  52. data/sorbet/rbi/gems/nokogiri.rbi +1011 -0
  53. data/sorbet/rbi/gems/public_suffix.rbi +104 -0
  54. data/sorbet/rbi/gems/rake.rbi +646 -0
  55. data/sorbet/rbi/gems/rspec-core.rbi +1893 -0
  56. data/sorbet/rbi/gems/rspec-expectations.rbi +1123 -0
  57. data/sorbet/rbi/gems/rspec-mocks.rbi +1090 -0
  58. data/sorbet/rbi/gems/rspec-support.rbi +280 -0
  59. data/sorbet/rbi/gems/rspec.rbi +15 -0
  60. data/sorbet/rbi/gems/safe_yaml.rbi +124 -0
  61. data/sorbet/rbi/gems/thread_safe.rbi +82 -0
  62. data/sorbet/rbi/gems/tzinfo.rbi +406 -0
  63. data/sorbet/rbi/gems/webmock.rbi +532 -0
  64. data/sorbet/rbi/hidden-definitions/hidden.rbi +13722 -0
  65. data/sorbet/rbi/sorbet-typed/lib/activesupport/all/activesupport.rbi +1431 -0
  66. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8684 -0
  67. data/sorbet/rbi/sorbet-typed/lib/httparty/all/httparty.rbi +427 -0
  68. data/sorbet/rbi/sorbet-typed/lib/minitest/all/minitest.rbi +108 -0
  69. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  70. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  71. data/sorbet/rbi/todo.rbi +11 -0
  72. 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,2 @@
1
+ # typed: strict
2
+ require 'setsuzoku/rspec/dynamic_spec_helper'
@@ -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