mno-enterprise-core 2.0.0

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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/Rakefile +12 -0
  4. data/app/assets/images/mno_enterprise/main-logo.png +0 -0
  5. data/app/controllers/mno_enterprise/application_controller.rb +116 -0
  6. data/app/helpers/mno_enterprise/application_helper.rb +67 -0
  7. data/app/helpers/mno_enterprise/impersonate_helper.rb +27 -0
  8. data/app/models/mno_enterprise/ability.rb +6 -0
  9. data/app/models/mno_enterprise/app.rb +72 -0
  10. data/app/models/mno_enterprise/app_instance.rb +36 -0
  11. data/app/models/mno_enterprise/app_instances_sync.rb +6 -0
  12. data/app/models/mno_enterprise/arrears_situation.rb +6 -0
  13. data/app/models/mno_enterprise/audit_event.rb +21 -0
  14. data/app/models/mno_enterprise/base_resource.rb +228 -0
  15. data/app/models/mno_enterprise/credit_card.rb +40 -0
  16. data/app/models/mno_enterprise/deletion_request.rb +35 -0
  17. data/app/models/mno_enterprise/impac/dashboard.rb +36 -0
  18. data/app/models/mno_enterprise/impac/dashboard_provisioner.rb +5 -0
  19. data/app/models/mno_enterprise/impac/kpi.rb +9 -0
  20. data/app/models/mno_enterprise/impac/widget.rb +13 -0
  21. data/app/models/mno_enterprise/invoice.rb +53 -0
  22. data/app/models/mno_enterprise/org_invite.rb +50 -0
  23. data/app/models/mno_enterprise/organization.rb +33 -0
  24. data/app/models/mno_enterprise/team.rb +50 -0
  25. data/app/models/mno_enterprise/tenant.rb +5 -0
  26. data/app/models/mno_enterprise/tenant_invoice.rb +5 -0
  27. data/app/models/mno_enterprise/user.rb +183 -0
  28. data/app/pdf/mno_enterprise/invoice_pdf.rb +516 -0
  29. data/config/initializers/audit_log.rb +5 -0
  30. data/config/locales/devise.en.yml +60 -0
  31. data/config/routes.rb +2 -0
  32. data/config/styleguide.yml +106 -0
  33. data/lib/accountingjs_serializer.rb +51 -0
  34. data/lib/devise/controllers/extension_helpers.rb +52 -0
  35. data/lib/devise/extension_routes.rb +11 -0
  36. data/lib/devise/hooks/password_expirable.rb +5 -0
  37. data/lib/devise/models/password_expirable.rb +28 -0
  38. data/lib/devise/models/remote_authenticatable.rb +48 -0
  39. data/lib/devise/strategies/remote_authenticatable.rb +44 -0
  40. data/lib/devise_extension.rb +36 -0
  41. data/lib/faraday/adapter/net_http_no_proxy.rb +19 -0
  42. data/lib/generators/mno_enterprise/database_extension/USAGE +11 -0
  43. data/lib/generators/mno_enterprise/database_extension/database_extension_generator.rb +36 -0
  44. data/lib/generators/mno_enterprise/database_extension/templates/model.rb +9 -0
  45. data/lib/generators/mno_enterprise/dummy/dummy_generator.rb +98 -0
  46. data/lib/generators/mno_enterprise/dummy/templates/rails/application.rb.erb +9 -0
  47. data/lib/generators/mno_enterprise/dummy/templates/rails/boot.rb.erb +6 -0
  48. data/lib/generators/mno_enterprise/dummy/templates/rails/database.yml +22 -0
  49. data/lib/generators/mno_enterprise/dummy/templates/rails/routes.rb +8 -0
  50. data/lib/generators/mno_enterprise/dummy/templates/rails/test-env.rb +45 -0
  51. data/lib/generators/mno_enterprise/install/install_generator.rb +140 -0
  52. data/lib/generators/mno_enterprise/install/templates/Procfile +1 -0
  53. data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +135 -0
  54. data/lib/generators/mno_enterprise/install/templates/config/mno_enterprise_styleguide.yml +104 -0
  55. data/lib/generators/mno_enterprise/install/templates/javascripts/mno_enterprise_extensions.js +7 -0
  56. data/lib/generators/mno_enterprise/install/templates/stylesheets/main.less_erb +25 -0
  57. data/lib/generators/mno_enterprise/install/templates/stylesheets/theme.less_erb +59 -0
  58. data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +337 -0
  59. data/lib/generators/mno_enterprise/install/templates/tasks/sprites.rake +14 -0
  60. data/lib/generators/mno_enterprise/puma_stack/puma_stack_generator.rb +58 -0
  61. data/lib/generators/mno_enterprise/templates/scripts/monit/app-server.conf +8 -0
  62. data/lib/generators/mno_enterprise/templates/scripts/nginx/app +51 -0
  63. data/lib/generators/mno_enterprise/templates/scripts/puma.rb +25 -0
  64. data/lib/generators/mno_enterprise/templates/scripts/setup.sh +27 -0
  65. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-hotrestart.conf +26 -0
  66. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-server.conf +34 -0
  67. data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web.conf +2 -0
  68. data/lib/generators/mno_enterprise/templates/scripts/upstart/app.conf +11 -0
  69. data/lib/her_extension/her_orm_adapter.rb +54 -0
  70. data/lib/her_extension/middleware/mnoe_api_v1_parse_json.rb +54 -0
  71. data/lib/her_extension/model/associations/association.rb +61 -0
  72. data/lib/her_extension/model/associations/association_proxy.rb +34 -0
  73. data/lib/her_extension/model/associations/has_many_association.rb +115 -0
  74. data/lib/her_extension/model/attributes.rb +43 -0
  75. data/lib/her_extension/model/orm.rb +59 -0
  76. data/lib/her_extension/model/parse.rb +40 -0
  77. data/lib/her_extension/model/relation.rb +92 -0
  78. data/lib/her_extension/validations/remote_uniqueness_validation.rb +33 -0
  79. data/lib/html_processor.rb +106 -0
  80. data/lib/mandrill_client.rb +58 -0
  81. data/lib/mno-enterprise-core.rb +1 -0
  82. data/lib/mno_enterprise/concerns.rb +4 -0
  83. data/lib/mno_enterprise/concerns/controllers.rb +6 -0
  84. data/lib/mno_enterprise/concerns/controllers/angular_csrf.rb +59 -0
  85. data/lib/mno_enterprise/concerns/controllers/auth.rb +9 -0
  86. data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +187 -0
  87. data/lib/mno_enterprise/concerns/controllers/auth/passwords_controller.rb +54 -0
  88. data/lib/mno_enterprise/concerns/controllers/auth/registrations_controller.rb +136 -0
  89. data/lib/mno_enterprise/concerns/controllers/auth/sessions_controller.rb +54 -0
  90. data/lib/mno_enterprise/concerns/controllers/auth/unlocks_controller.rb +50 -0
  91. data/lib/mno_enterprise/concerns/models.rb +6 -0
  92. data/lib/mno_enterprise/concerns/models/ability.rb +108 -0
  93. data/lib/mno_enterprise/concerns/models/app_instance.rb +100 -0
  94. data/lib/mno_enterprise/concerns/models/organization.rb +102 -0
  95. data/lib/mno_enterprise/core.rb +279 -0
  96. data/lib/mno_enterprise/database_extendable.rb +57 -0
  97. data/lib/mno_enterprise/engine.rb +33 -0
  98. data/lib/mno_enterprise/testing_support/ability_test_helper.rb +10 -0
  99. data/lib/mno_enterprise/testing_support/common_rake.rb +19 -0
  100. data/lib/mno_enterprise/testing_support/factories.rb +13 -0
  101. data/lib/mno_enterprise/testing_support/factories/app_instances.rb +30 -0
  102. data/lib/mno_enterprise/testing_support/factories/apps.rb +45 -0
  103. data/lib/mno_enterprise/testing_support/factories/arrears_situation.rb +14 -0
  104. data/lib/mno_enterprise/testing_support/factories/audit_event.rb +15 -0
  105. data/lib/mno_enterprise/testing_support/factories/credit_card.rb +33 -0
  106. data/lib/mno_enterprise/testing_support/factories/deletion_request.rb +17 -0
  107. data/lib/mno_enterprise/testing_support/factories/impac/dashboards.rb +15 -0
  108. data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +20 -0
  109. data/lib/mno_enterprise/testing_support/factories/impac/widgets.rb +15 -0
  110. data/lib/mno_enterprise/testing_support/factories/invoices.rb +51 -0
  111. data/lib/mno_enterprise/testing_support/factories/org_invite.rb +24 -0
  112. data/lib/mno_enterprise/testing_support/factories/organizations.rb +25 -0
  113. data/lib/mno_enterprise/testing_support/factories/team.rb +17 -0
  114. data/lib/mno_enterprise/testing_support/factories/tenant.rb +12 -0
  115. data/lib/mno_enterprise/testing_support/factories/tenant_invoice.rb +29 -0
  116. data/lib/mno_enterprise/testing_support/factories/users.rb +48 -0
  117. data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +49 -0
  118. data/lib/mno_enterprise/testing_support/mno_enterprise_api_test_helper.rb +167 -0
  119. data/lib/mno_enterprise/testing_support/mnoe_faraday_test_adapter.rb +173 -0
  120. data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +175 -0
  121. data/lib/mno_enterprise/testing_support/user_action_shared.rb +47 -0
  122. data/lib/mno_enterprise/version.rb +3 -0
  123. data/lib/tasks/mno_enterprise_tasks.rake +22 -0
  124. data/spec/controllers/mno_enterprise/angular_csrf_spec.rb +42 -0
  125. data/spec/lib/her_extension/her_orm_adapter.rb +7 -0
  126. data/spec/lib/her_extension/model/relation_spec.rb +7 -0
  127. data/spec/lib/mandrill_client_spec.rb +64 -0
  128. data/spec/mno_enterprise_spec.rb +79 -0
  129. data/spec/models/mno_enterprise/app_instance_spec.rb +7 -0
  130. data/spec/models/mno_enterprise/app_spec.rb +62 -0
  131. data/spec/models/mno_enterprise/base_resource_spec.rb +28 -0
  132. data/spec/models/mno_enterprise/deletion_request_spec.rb +26 -0
  133. data/spec/models/mno_enterprise/invoice_spec.rb +7 -0
  134. data/spec/models/mno_enterprise/organization_spec.rb +7 -0
  135. data/spec/models/mno_enterprise/user_spec.rb +44 -0
  136. data/spec/rails_helper.rb +73 -0
  137. data/spec/spec_helper.rb +78 -0
  138. metadata +421 -0
@@ -0,0 +1,17 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :mno_enterprise_team, :class => 'MnoEnterprise::Team' do
5
+
6
+ factory :team, class: MnoEnterprise::Team do
7
+ sequence(:id)
8
+ sequence(:name) { |n| "Team#{n}" }
9
+
10
+ created_at 3.days.ago
11
+ updated_at 1.hour.ago
12
+
13
+ # Properly build the resource with Her
14
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ FactoryGirl.define do
2
+
3
+ factory :tenant, class: MnoEnterprise::Tenant do
4
+ last_portfolio_amount Money.new(65644,'AUD')
5
+ last_customers_invoicing_amount Money.new(687994,'AUD')
6
+ last_customers_outstanding_amount Money.new(178986,'AUD')
7
+ last_commission_amount Money.new(412345,'AUD')
8
+
9
+ # Properly build the resource with Her
10
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ FactoryGirl.define do
4
+ factory :mno_enterprise_tenant_invoice, :class => 'TenantInvoice' do
5
+
6
+
7
+ factory :tenant_invoice, class: MnoEnterprise::TenantInvoice do
8
+ sequence(:id)
9
+ sequence(:slug) { |n| "201504-NU#{n}" }
10
+ organization_id 265
11
+
12
+ started_at 28.days.ago
13
+ ended_at 3.days.ago
14
+ created_at 3.days.ago
15
+ updated_at 1.hour.ago
16
+ paid_at nil
17
+
18
+ total_commission_amount Money.new(0,'AUD')
19
+ total_portfolio_amount Money.new(0,'AUD')
20
+ non_commissionable_amount Money.new(0,'AUD')
21
+ mno_commission_amount Money.new(0,'AUD')
22
+
23
+ # Properly build the resource with Her
24
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
25
+ end
26
+
27
+
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ # Read about factories at https://github.com/thoughtbot/factory_girl
2
+
3
+ # This is an API resource factory generating a Hash to be used in API stubs
4
+ # Use as such: build(:api_user)
5
+ # See http://stackoverflow.com/questions/10032760/how-to-define-an-array-hash-in-factory-girl
6
+ FactoryGirl.define do
7
+
8
+ factory :user, class: MnoEnterprise::User do
9
+ sequence(:id)
10
+ sequence(:uid) { |n| "usr-fda9#{n}" }
11
+ name "John"
12
+ surname "Doe"
13
+ sequence(:email) { |n| "john.doe#{n}@maestrano.com" }
14
+ company "Doe Inc."
15
+ phone "449 789 456"
16
+ phone_country_code "AU"
17
+ geo_country_code "AU"
18
+ geo_state_code "NSW"
19
+ geo_city "Sydney"
20
+ created_at 2.days.ago
21
+ updated_at 2.days.ago
22
+ sso_session "1fdd5sf5a73D7sd1as2a4sd541"
23
+ admin_role false
24
+
25
+ confirmation_sent_at 2.days.ago
26
+ confirmation_token "wky763pGjtzWR7dP44PD"
27
+ confirmed_at 1.days.ago
28
+
29
+ trait :unconfirmed do
30
+ confirmed_at nil
31
+ end
32
+
33
+ trait :admin do
34
+ admin_role true
35
+ end
36
+
37
+ trait :with_deletion_request do
38
+ deletion_request { build(:deletion_request).attributes }
39
+ end
40
+
41
+ trait :with_organizations do
42
+ organizations { [build(:organization).attributes] }
43
+ end
44
+
45
+ # Properly build the resource with Her
46
+ initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } }
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ module MnoEnterprise::TestingSupport::JpiV1TestHelper
2
+
3
+ # Expect 'user' to be assigned
4
+ shared_examples "jpi v1 protected action" do
5
+ context "with guest user" do
6
+ it "prevents access" do
7
+ sign_out user
8
+ expect(subject).to_not be_successful
9
+ expect(subject.code).to eq('401')
10
+ end
11
+ end
12
+
13
+ context 'with signed in user' do
14
+ it "authorizes access" do
15
+ sign_in user
16
+ expect(subject).to be_successful
17
+ end
18
+ end
19
+ end
20
+
21
+ # Expect 'user' to be assigned
22
+ # Expect 'ability' to be assigned
23
+ shared_examples "jpi v1 authorizable action" do
24
+ context "with guest user" do
25
+ it "prevents access" do
26
+ sign_out user
27
+ expect(subject).to_not be_successful
28
+ expect(subject.code).to eq('401')
29
+ end
30
+ end
31
+
32
+ context 'with unauthorized signed in user' do
33
+ it "prevents access" do
34
+ sign_in user
35
+ allow(ability).to receive(:can?).with(any_args).and_return(false)
36
+ expect(subject).to_not be_successful
37
+ end
38
+ end
39
+
40
+ context 'with authorized signed in user' do
41
+ it "authorizes access" do
42
+ sign_in user
43
+ allow(ability).to receive(:can?).with(any_args).and_return(true)
44
+ expect(subject).to be_successful
45
+ end
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,167 @@
1
+ module MnoEnterpriseApiTestHelper
2
+
3
+ # Take a resource and transform it into a Hash describing
4
+ # the resource as if it had been returned by the MnoEnterprise
5
+ # API server
6
+ def from_api(res)
7
+ { data: serialize_type(res), metadata: {pagination: {count: entity_count(res)}} }
8
+ end
9
+
10
+ def serialize_type(res)
11
+ case
12
+ when res.kind_of?(Array)
13
+ return res.map { |e| serialize_type(e) }
14
+ when res.kind_of?(MnoEnterprise::BaseResource)
15
+ hash = res.attributes.dup
16
+ hash.each do |k,v|
17
+ hash[k] = serialize_type(v)
18
+ end
19
+ return hash
20
+ when res.kind_of?(Hash)
21
+ hash = res.dup
22
+ hash.each do |k,v|
23
+ hash[k] = serialize_type(v)
24
+ end
25
+ return hash
26
+ when res.kind_of?(Money)
27
+ return { cents: res.cents, currency: res.currency_as_string }
28
+ when res.respond_to?(:iso8601)
29
+ return res.iso8601
30
+ else
31
+ return res
32
+ end
33
+ end
34
+
35
+ def entity_count(res)
36
+ case
37
+ when res.kind_of?(Array)
38
+ return res.count
39
+ when res.kind_of?(Hash)
40
+ return res.count
41
+ else
42
+ return 1
43
+ end
44
+ end
45
+
46
+ # Reset all API stubs.
47
+ # Called before each test (see spec_helper)
48
+ def api_stub_reset
49
+ @_api_stub = nil
50
+ @_stub_list = {}
51
+ api_stub_configure(Her::API.new)
52
+ end
53
+
54
+ # Example usage:
55
+ #
56
+ # Without opts, it yields a faraday stub object which you can configure
57
+ # manually:
58
+ #
59
+ # You can also pass the response stub via opts
60
+ # api_stub_for(User,
61
+ # path: '/users/popular',
62
+ # response: [{ id: 1, name: "Tobias Fünke" }, { id: 2, name: "Lindsay Fünke" }]
63
+ # )
64
+ #
65
+ # You can also specify the response code:
66
+ # api_stub_for(User,
67
+ # path: '/users/popular',
68
+ # code: 200,
69
+ # response: [{ id: 1, name: "Tobias Fünke" }, { id: 2, name: "Lindsay Fünke" }]
70
+ # )
71
+ def api_stub_for(klass, opts = {})
72
+ real_opts = klass
73
+ if klass.is_a?(Class)
74
+ warn("DEPRECATION WARNING: api_stub_for(MyClass,{ some: 'opts'}) is deprecated. Please use api_stub_for({ some: 'opts' }) from now on")
75
+ real_opts = opts
76
+ end
77
+
78
+ set_api_stub
79
+ api_stub_add(real_opts)
80
+ api_stub_configure(@_api_stub)
81
+ end
82
+
83
+ private
84
+ # Set a stub api on the provider class
85
+ def set_api_stub
86
+ return @_api_stub if @_api_stub
87
+ @_api_stub = Her::API.new
88
+ allow(MnoEnterprise::BaseResource).to receive(:her_api).and_return(@_api_stub = Her::API.new)
89
+ @_api_stub
90
+ end
91
+
92
+ # Add a stub to the api
93
+ # E.g.:
94
+ # {
95
+ # path: '/users/popular',
96
+ # code: 200,
97
+ # response: [{ id: 1, name: "Tobias Fünke" }, { id: 2, name: "Lindsay Fünke" }]
98
+ # }
99
+ def api_stub_add(orig_opts)
100
+ @_stub_list ||= {}
101
+ opts = orig_opts.dup
102
+
103
+ # Expand options so that: { put: '/path' } becomes { path: '/path', method: :put }
104
+ unless opts[:method] && opts[:path]
105
+ [:get,:put,:post,:delete].each do |verb|
106
+ if path = opts.delete(verb)
107
+ opts[:path] = path
108
+ opts[:method] = verb
109
+ end
110
+ end
111
+ end
112
+
113
+ key = opts.to_param
114
+ @_stub_list[key] = opts
115
+ end
116
+
117
+ # Configure the api and apply a list of stubs
118
+ def api_stub_configure(api)
119
+ # This block should match the her.rb initializer
120
+ api.setup MnoEnterprise.send(:api_options).merge(url: "http://localhost:65000") do |c|
121
+ # Request
122
+ c.use Faraday::Request::BasicAuthentication, MnoEnterprise.tenant_id, MnoEnterprise.tenant_key
123
+ c.use Faraday::Request::UrlEncoded
124
+
125
+ # Response
126
+ c.use Her::Middleware::MnoeApiV1ParseJson
127
+
128
+ # Add stubs on the test adapter
129
+ c.use MnoeFaradayTestAdapter do |receiver|
130
+ @_stub_list.each do |key,stub|
131
+ params = stub[:params] && stub[:params].any? ? "?#{stub[:params].to_param}" : ""
132
+ path = "#{stub[:path]}#{params}"
133
+
134
+ receiver.send(stub[:method] || :get,path) { |env|
135
+ body = Rack::Utils.parse_nested_query(env.body)
136
+
137
+ # respond_with takes a model in argument and automatically responds with
138
+ # a json representation of the model
139
+ # If the action is an update, it attempts to update the model
140
+ if model = stub[:respond_with]
141
+ model.assign_attributes(body['data']) if stub[:method] == :put && model.respond_to?(:assign_attributes) && body['data']
142
+ resp = from_api(model)
143
+ else
144
+ if stub[:response].is_a?(Proc)
145
+ args = stub[:response].arity > 0 ? [body] : []
146
+ resp = stub[:response].call(*args)
147
+ else
148
+ resp = stub[:response] || {}
149
+ end
150
+ end
151
+
152
+ # Response code
153
+ if stub[:code].is_a?(Proc)
154
+ args = stub[:code].arity > 0 ? [body] : []
155
+ resp_code = stub[:code].call(*args)
156
+ else
157
+ resp_code = stub[:code] || 200
158
+ end
159
+
160
+
161
+ [resp_code, {}, resp.to_json]
162
+ }
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,173 @@
1
+ # Adapter and modified from: https://github.com/lostisland/faraday/blob/master/lib/faraday/adapter/test.rb
2
+ #
3
+ #
4
+ #
5
+ # test = Faraday::Connection.new do
6
+ # use MnoeFaradayTestAdapter do |stub|
7
+ # stub.get '/nigiri/sake.json' do
8
+ # [200, {}, 'hi world']
9
+ # end
10
+ # end
11
+ # end
12
+ #
13
+ # resp = test.get '/nigiri/sake.json'
14
+ # resp.body # => 'hi world'
15
+ #
16
+ class MnoeFaradayTestAdapter < Faraday::Adapter
17
+ attr_accessor :stubs
18
+
19
+ class Stubs
20
+ class NotFound < StandardError
21
+ end
22
+
23
+ def initialize
24
+ # {:get => [Stub, Stub]}
25
+ @stack, @consumed = {}, {}
26
+ yield(self) if block_given?
27
+ end
28
+
29
+ def empty?
30
+ @stack.empty?
31
+ end
32
+
33
+ def match(request_method, path, headers, body)
34
+ return false if !@stack.key?(request_method)
35
+ stack = @stack[request_method]
36
+ consumed = (@consumed[request_method] ||= [])
37
+
38
+ if stub = matches?(stack, path, headers, body)
39
+ consumed << stack.delete(stub)
40
+ stub
41
+ else
42
+ matches?(consumed, path, headers, body)
43
+ end
44
+ end
45
+
46
+ def get(path, headers = {}, &block)
47
+ new_stub(:get, path, headers, &block)
48
+ end
49
+
50
+ def head(path, headers = {}, &block)
51
+ new_stub(:head, path, headers, &block)
52
+ end
53
+
54
+ def post(path, body=nil, headers = {}, &block)
55
+ new_stub(:post, path, headers, body, &block)
56
+ end
57
+
58
+ def put(path, body=nil, headers = {}, &block)
59
+ new_stub(:put, path, headers, body, &block)
60
+ end
61
+
62
+ def patch(path, body=nil, headers = {}, &block)
63
+ new_stub(:patch, path, headers, body, &block)
64
+ end
65
+
66
+ def delete(path, headers = {}, &block)
67
+ new_stub(:delete, path, headers, &block)
68
+ end
69
+
70
+ def options(path, headers = {}, &block)
71
+ new_stub(:options, path, headers, &block)
72
+ end
73
+
74
+ # Raises an error if any of the stubbed calls have not been made.
75
+ def verify_stubbed_calls
76
+ failed_stubs = []
77
+ @stack.each do |method, stubs|
78
+ unless stubs.size == 0
79
+ failed_stubs.concat(stubs.map {|stub|
80
+ "Expected #{method} #{stub}."
81
+ })
82
+ end
83
+ end
84
+ raise failed_stubs.join(" ") unless failed_stubs.size == 0
85
+ end
86
+
87
+ protected
88
+
89
+ def new_stub(request_method, path, headers = {}, body=nil, &block)
90
+ normalized_path = Faraday::Utils.normalize_path(path)
91
+ (@stack[request_method] ||= []) << Stub.new(normalized_path, headers, body, block)
92
+ end
93
+
94
+ def matches?(stack, path, headers, body)
95
+ stack.detect { |stub| stub.matches?(path, headers, body) }
96
+ end
97
+ end
98
+
99
+ class Stub < Struct.new(:path, :params, :headers, :body, :block)
100
+ def initialize(full, headers, body, block)
101
+ path, query = full.split('?')
102
+ params = query ?
103
+ Faraday::Utils.parse_nested_query(query) :
104
+ {}
105
+ super(path, params, headers, body, block)
106
+ end
107
+
108
+ def matches?(request_uri, request_headers, request_body)
109
+ request_path, request_query = request_uri.split('?')
110
+ request_params = request_query ?
111
+ Faraday::Utils.parse_nested_query(request_query) :
112
+ {}
113
+ request_path == path &&
114
+ params_match?(request_params) &&
115
+ (body.to_s.size.zero? || request_body == body) &&
116
+ headers_match?(request_headers)
117
+ end
118
+
119
+ def params_match?(request_params)
120
+ params_deep_equal?(params,request_params)
121
+ # params.keys.all? do |key|
122
+ # request_params[key] == params[key]
123
+ # end
124
+ end
125
+
126
+ def params_deep_equal?(src,dst)
127
+ src.keys.all? do |key|
128
+ if src[key] && dst[key] && src[key].is_a?(Hash) && dst[key].is_a?(Hash)
129
+ params_deep_equal?(src[key],dst[key])
130
+ else
131
+ (src[key] == '**' && dst[key]) || src[key] == dst[key]
132
+ end
133
+ end
134
+ end
135
+
136
+ def headers_match?(request_headers)
137
+ headers.keys.all? do |key|
138
+ request_headers[key] == headers[key]
139
+ end
140
+ end
141
+
142
+ def to_s
143
+ "#{path} #{body}"
144
+ end
145
+ end
146
+
147
+ def initialize(app, stubs=nil, &block)
148
+ super(app)
149
+ @stubs = stubs || Stubs.new
150
+ configure(&block) if block
151
+ end
152
+
153
+ def configure
154
+ yield(stubs)
155
+ end
156
+
157
+ def call(env)
158
+ super
159
+ normalized_path = Faraday::Utils.normalize_path(env[:url])
160
+ params_encoder = env.request.params_encoder || Faraday::Utils.default_params_encoder
161
+
162
+ if stub = stubs.match(env[:method], normalized_path, env.request_headers, env[:body])
163
+ env[:params] = (query = env[:url].query) ?
164
+ params_encoder.decode(query) :
165
+ {}
166
+ status, headers, body = stub.block.call(env)
167
+ save_response(env, status, body, headers)
168
+ else
169
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
170
+ end
171
+ @app.call(env)
172
+ end
173
+ end