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