lhs 21.2.1 → 21.3.0.pre.autoauth.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -0
  3. data/lib/lhs.rb +8 -0
  4. data/lib/lhs/concerns/autoload_records.rb +20 -3
  5. data/lib/lhs/concerns/o_auth.rb +25 -0
  6. data/lib/lhs/concerns/record/configuration.rb +9 -1
  7. data/lib/lhs/concerns/record/request.rb +25 -9
  8. data/lib/lhs/config.rb +1 -1
  9. data/lib/lhs/interceptors/auto_oauth/interceptor.rb +18 -0
  10. data/lib/lhs/interceptors/auto_oauth/thread_registry.rb +18 -0
  11. data/lib/lhs/version.rb +1 -1
  12. data/spec/auto_oauth_spec.rb +51 -0
  13. data/spec/autoloading_spec.rb +35 -8
  14. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  15. data/spec/dummy/app/controllers/automatic_authentication_controller.rb +11 -0
  16. data/spec/dummy/app/controllers/error_handling_with_chains_controller.rb +2 -2
  17. data/spec/dummy/app/controllers/extended_rollbar_controller.rb +2 -2
  18. data/spec/dummy/app/controllers/option_blocks_controller.rb +2 -2
  19. data/spec/dummy/app/models/dummy_customer.rb +6 -0
  20. data/spec/dummy/app/models/{record.rb → dummy_record.rb} +1 -1
  21. data/spec/dummy/app/models/dummy_record_with_oauth.rb +7 -0
  22. data/spec/dummy/app/models/{user.rb → dummy_user.rb} +1 -1
  23. data/spec/dummy/app/models/providers/customer_system.rb +7 -0
  24. data/spec/dummy/config/initializers/lhs.rb +3 -0
  25. data/spec/dummy/config/routes.rb +3 -0
  26. data/spec/option_blocks/ensure_reset_between_requests_spec.rb +2 -1
  27. data/spec/record/error_handling_integration_spec.rb +1 -1
  28. data/spec/record/includes_spec.rb +59 -0
  29. data/spec/request_cycle_cache_spec.rb +3 -3
  30. data/spec/support/reset.rb +31 -9
  31. metadata +23 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cdee54df382d303ac6c2df71e4dfb3ce6816d098578fc1919262b2daa937763c
4
- data.tar.gz: '0340809599cfa8cb547c85dd031f8e767a644885637e77bcdc09ad011e2be617'
3
+ metadata.gz: a8391f6f7e78b219c2f48ca87b7f160a28feab0f5e113553a41b339d8f3b72df
4
+ data.tar.gz: 30e48a8d78cca4d1ed76595cfd72c90bda2cfd77873590254bdf92685df97df1
5
5
  SHA512:
6
- metadata.gz: e01af57617fc98df8e61a053bb4b69a527701a29aa1cfd3b842ec1b08ca47e925702ffeedf31b019997a1693409fc00637da0dba4cc205b0cfafba0f1b2769c2
7
- data.tar.gz: 0f7418cee11af1966be59ad017f13469aaa94a96e4214045c05434312690b5d9fad08d4193b8705844e0d5f3411a1988498b50a966cf7324c167f9062493aa8a
6
+ metadata.gz: 00f6961b5546311a919830c913153ecfcbbbeec7a67079db66284ec9280caa6656ea023db44945b0387a0cf02b372736e20632318315f0b3d87811ded26f6aa8
7
+ data.tar.gz: 56c2dd947ba8c5b82800997198c7185bc4d331195d3d1040b779431569ece6bed89eb6822d98bf80aa880482e176f6337cf98518ee176c951363644ce01c2d64
data/README.md CHANGED
@@ -2444,6 +2444,66 @@ LHS.configure do |config|
2444
2444
  end
2445
2445
  ```
2446
2446
 
2447
+ ## Automatic Authentication (OAuth)
2448
+
2449
+ LHS provides a way to have records automatically fetch and use OAuth authentication when performing requests within Rails.
2450
+
2451
+ In order to enable automatic oauth authentication, perform the following steps:
2452
+
2453
+ 1. Make sure LHS is configured to perform `auto_oauth`. Provide a block that when executed in the controller context returns a valid access_token/bearer_token.
2454
+ ```ruby
2455
+ # config/initializers/lhs.rb
2456
+
2457
+ LHS.configure do |config|
2458
+ config.auto_oauth = -> { access_token }
2459
+ end
2460
+ ```
2461
+
2462
+ 2. Opt-in records requiring oauth authentication:
2463
+
2464
+ ```ruby
2465
+ # app/models/record.rb
2466
+
2467
+ class Record < LHS::Record
2468
+ oauth
2469
+ # ...
2470
+ end
2471
+ ```
2472
+
2473
+ 3. Include the `LHS::OAuth` context into your application controller:
2474
+
2475
+ ```ruby
2476
+ # app/controllers/application_controller.rb
2477
+
2478
+ class ApplicationController < ActionController::Base
2479
+ include LHS::OAuth
2480
+
2481
+ # ...
2482
+ end
2483
+ ```
2484
+
2485
+ 4. Make sure you have the `LHC::Auth` interceptor enabled:
2486
+
2487
+ ```ruby
2488
+ # config/initializers/lhc.rb
2489
+
2490
+ LHC.configure do |config|
2491
+ config.interceptors = [LHC::Auth]
2492
+ end
2493
+ ```
2494
+
2495
+ Now you can perform requests based on the record that will be auto authenticated from now on:
2496
+
2497
+ ```ruby
2498
+ # app/controllers/some_controller.rb
2499
+
2500
+ Record.find(1)
2501
+ ```
2502
+ ```
2503
+ https://records/1
2504
+ Authentication: 'Bearer token-12345'
2505
+ ```
2506
+
2447
2507
  ## Option Blocks
2448
2508
 
2449
2509
  In order to apply options to all requests performed in a give block, LHS provides option blocks.
data/lib/lhs.rb CHANGED
@@ -22,6 +22,12 @@ module LHS
22
22
  autoload :Inspect,
23
23
  'lhs/concerns/inspect'
24
24
  module Interceptors
25
+ module AutoOauth
26
+ autoload :ThreadRegistry,
27
+ 'lhs/interceptors/auto_oauth/thread_registry'
28
+ autoload :Interceptor,
29
+ 'lhs/interceptors/auto_oauth/interceptor'
30
+ end
25
31
  module RequestCycleCache
26
32
  autoload :ThreadRegistry,
27
33
  'lhs/interceptors/request_cycle_cache/thread_registry'
@@ -41,6 +47,8 @@ module LHS
41
47
  'lhs/concerns/is_href'
42
48
  autoload :Item,
43
49
  'lhs/item'
50
+ autoload :OAuth,
51
+ 'lhs/concerns/o_auth.rb'
44
52
  autoload :OptionBlocks,
45
53
  'lhs/concerns/option_blocks'
46
54
  autoload :Pagination,
@@ -27,11 +27,28 @@ module AutoloadRecords
27
27
  @app.call(env)
28
28
  end
29
29
 
30
- def self.require_records
31
- Dir.glob(Rails.root.join('app', 'models', '**', '*.rb')).each do |file|
32
- require_dependency file if File.read(file).match('LHS::Record')
30
+ def self.model_files
31
+ Dir.glob(Rails.root.join('app', 'models', '**', '*.rb'))
32
+ end
33
+
34
+ def self.require_direct_inheritance
35
+ model_files.map do |file|
36
+ next unless File.read(file).match('LHS::Record')
37
+ require_dependency file
38
+ file.split('models/').last.gsub('.rb', '').classify
39
+ end.compact
40
+ end
41
+
42
+ def self.require_inheriting_records(parents)
43
+ model_files.each do |file|
44
+ next if parents.none? { |parent| File.read(file).match(parent) }
45
+ require_dependency file
33
46
  end
34
47
  end
48
+
49
+ def self.require_records
50
+ require_inheriting_records(require_direct_inheritance)
51
+ end
35
52
  end
36
53
  end
37
54
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module LHS
6
+ module OAuth
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ prepend_before_action :lhs_store_oauth_access_token
11
+ end
12
+
13
+ private
14
+
15
+ def lhs_store_oauth_access_token
16
+ lhs_check_auto_oauth_enabled!
17
+ LHS::Interceptors::AutoOauth::ThreadRegistry.access_token = instance_exec(&LHS.config.auto_oauth)
18
+ end
19
+
20
+ def lhs_check_auto_oauth_enabled!
21
+ return if LHS.config.auto_oauth.present? && LHS.config.auto_oauth.is_a?(Proc)
22
+ raise 'You have to enable LHS.config.auto_oauth by passing a proc returning an access token!'
23
+ end
24
+ end
25
+ end
@@ -13,7 +13,15 @@ class LHS::Record
13
13
 
14
14
  module ClassMethods
15
15
  def configuration(args)
16
- @configuration = args.freeze || {}
16
+ @configuration = args || {}
17
+ end
18
+
19
+ def auto_oauth?
20
+ LHS.config.auto_oauth && @configuration && @configuration.fetch(:auto_oauth, false)
21
+ end
22
+
23
+ def oauth
24
+ @configuration.present? ? @configuration.merge!(auto_oauth: true) : configuration(auto_oauth: true)
17
25
  end
18
26
 
19
27
  def item_key
@@ -176,7 +176,7 @@ class LHS::Record
176
176
  else
177
177
  handle_include(includes, data, nil, references[includes])
178
178
  end
179
- data.clear_cache! # as we just included new nested resources
179
+ data.clear_cache! if data.present? # as we just included new nested resources
180
180
  end
181
181
 
182
182
  def handle_include(included, data, sub_includes = nil, reference = nil)
@@ -246,7 +246,9 @@ class LHS::Record
246
246
  def skip_loading_includes?(data, included)
247
247
  if data.collection?
248
248
  data.to_a.none? { |item| item[included].present? }
249
- elsif data[included].present? && data[included].item? && data[included].href.blank?
249
+ elsif data.dig(included).blank?
250
+ true
251
+ elsif data[included].item? && data[included][:href].blank?
250
252
  true
251
253
  else
252
254
  !data._raw.key?(included)
@@ -515,20 +517,34 @@ class LHS::Record
515
517
  options[:url] = compute_url!(options[:params]) unless options.key?(:url)
516
518
  merge_explicit_params!(options[:params])
517
519
  options.delete(:params) if options[:params]&.empty?
518
- inject_request_cycle_cache!(options)
520
+ inject_interceptors!(options)
519
521
  options
520
522
  end
521
523
 
522
- # Injects options into request, that enable the request cycle cache interceptor
523
- def inject_request_cycle_cache!(options)
524
- return unless LHS.config.request_cycle_cache_enabled
524
+ def inject_interceptors!(options)
525
+ inject_interceptor!(
526
+ options,
527
+ LHS::Interceptors::RequestCycleCache::Interceptor,
528
+ LHC::Caching,
529
+ "[WARNING] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#caching-interceptor)!"
530
+ ) if LHS.config.request_cycle_cache_enabled
531
+
532
+ inject_interceptor!(
533
+ options,
534
+ LHS::Interceptors::AutoOauth::Interceptor,
535
+ LHC::Auth,
536
+ "[WARNING] Can't enable auto oauth as LHC::Auth interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#authentication-interceptor)!"
537
+ ) if self.auto_oauth?
538
+ end
539
+
540
+ def inject_interceptor!(options, interceptor, dependecy, warning)
525
541
  interceptors = options[:interceptors] || LHC.config.interceptors
526
- if interceptors.include?(LHC::Caching)
542
+ if interceptors.include?(dependecy)
527
543
  # Ensure interceptor is prepend
528
- interceptors = interceptors.unshift(LHS::Interceptors::RequestCycleCache::Interceptor)
544
+ interceptors = interceptors.unshift(interceptor)
529
545
  options[:interceptors] = interceptors
530
546
  else
531
- warn("[WARNING] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/docs/interceptors/caching.md#caching-interceptor)!")
547
+ warn(warning)
532
548
  end
533
549
  end
534
550
 
@@ -5,7 +5,7 @@ require 'singleton'
5
5
  class LHS::Config
6
6
  include Singleton
7
7
 
8
- attr_accessor :request_cycle_cache_enabled, :request_cycle_cache, :trace
8
+ attr_accessor :request_cycle_cache_enabled, :request_cycle_cache, :trace, :auto_oauth
9
9
 
10
10
  def initialize
11
11
  self.request_cycle_cache_enabled ||= true
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module LHS
6
+ module Interceptors
7
+ module AutoOauth
8
+ extend ActiveSupport::Concern
9
+
10
+ class Interceptor < LHC::Interceptor
11
+
12
+ def before_request
13
+ request.options[:auth] = { bearer: LHS::Interceptors::AutoOauth::ThreadRegistry.access_token }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/per_thread_registry'
5
+
6
+ module LHS
7
+ module Interceptors
8
+ module AutoOauth
9
+ extend ActiveSupport::Concern
10
+ class ThreadRegistry
11
+ # Using ActiveSupports PerThreadRegistry to be able to support Active Support v4.
12
+ # Will switch to thread_mattr_accessor (which comes with Activesupport) when we dropping support for Active Support v4.
13
+ extend ActiveSupport::PerThreadRegistry
14
+ attr_accessor :access_token
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHS
4
- VERSION = '21.2.1'
4
+ VERSION = '21.3.0.pre.autoauth.1'
5
5
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe 'Auto OAuth Authentication', type: :request, dummy_models: true do
6
+
7
+ context 'without LHC::Auth interceptor enabled' do
8
+
9
+ it 'shows a warning that it can not perform auto authentication' do
10
+ expect(lambda do
11
+ get '/automatic_authentication/oauth'
12
+ end).to output(
13
+ %r{\[WARNING\] Can't enable auto oauth as LHC::Auth interceptor is not enabled\/configured \(see https://github.com/local-ch/lhc/blob/master/README.md#authentication-interceptor\)!}
14
+ ).to_stderr
15
+ end
16
+ end
17
+
18
+ context 'with LHC::Auth interceptor enabled' do
19
+ let(:record_request) do
20
+ stub_request(:get, "http://datastore/v2/records_with_oauth/1")
21
+ .with(
22
+ headers: { 'Authorization' => "Bearer #{ApplicationController::ACCESS_TOKEN}" }
23
+ ).to_return(status: 200, body: { name: 'Record' }.to_json)
24
+ end
25
+
26
+ let(:records_request) do
27
+ stub_request(:get, "http://datastore/v2/records_with_oauth?color=blue")
28
+ .with(
29
+ headers: { 'Authorization' => "Bearer #{ApplicationController::ACCESS_TOKEN}" }
30
+ ).to_return(status: 200, body: { items: [ { name: 'Record' } ] }.to_json)
31
+ end
32
+
33
+ before do
34
+ LHC.configure do |config|
35
+ config.interceptors = [LHC::Auth]
36
+ end
37
+ record_request
38
+ records_request
39
+ end
40
+
41
+ after do
42
+ LHC.config.reset
43
+ end
44
+
45
+ it 'applies OAuth credentials for the individual request automatically' do
46
+ get '/automatic_authentication/oauth'
47
+ expect(record_request).to have_been_requested
48
+ expect(records_request).to have_been_requested
49
+ end
50
+ end
51
+ end
@@ -4,18 +4,45 @@ require "rails_helper"
4
4
 
5
5
  describe LHS, type: :request do
6
6
  context 'autoloading' do
7
- it "pre/re-loads all LHS classes initialy,|
8
- because it's necessary for endpoint-to-record-class-discovery",
9
- reset_before: false do
10
- all_endpoints = LHS::Record::Endpoints.all
11
- expect(all_endpoints['http://datastore/v2/users']).to be_present
12
- expect(all_endpoints['http://datastore/v2/users/{id}']).to be_present
7
+
8
+ let(:endpoints) { LHS::Record::Endpoints.all }
9
+
10
+ it "pre/re-loads all LHS classes initialy, because it's necessary for endpoint-to-record-class-discovery", reset_before: false do
11
+
12
+ expect(endpoints['http://datastore/v2/users']).to be_present
13
+ expect(endpoints['http://datastore/v2/users/{id}']).to be_present
14
+
13
15
  expect(
14
- User.endpoints.detect { |endpoint| endpoint.url == 'http://datastore/v2/users' }
16
+ DummyUser.endpoints.detect { |endpoint| endpoint.url == 'http://datastore/v2/users' }
15
17
  ).to be_present
16
18
  expect(
17
- User.endpoints.detect { |endpoint| endpoint.url == 'http://datastore/v2/users/{id}' }
19
+ DummyUser.endpoints.detect { |endpoint| endpoint.url == 'http://datastore/v2/users/{id}' }
18
20
  ).to be_present
19
21
  end
22
+
23
+ it "also pre/re-loads all LHS classes that inherited from an LHS provider, because it's necessary for endpoint-to-record-class-discovery", reset_before: false do
24
+
25
+ expect(endpoints['http://customers']).to be_present
26
+ expect(endpoints['http://customers/{id}']).to be_present
27
+
28
+ expect(
29
+ DummyCustomer.endpoints.detect { |endpoint| endpoint.url == 'http://customers' }
30
+ ).to be_present
31
+ expect(
32
+ DummyCustomer.endpoints.detect { |endpoint| endpoint.url == 'http://customers/{id}' }
33
+ ).to be_present
34
+
35
+ customer_request = stub_request(:get, "http://customers/1")
36
+ .with(
37
+ headers: {
38
+ 'Authorization' => 'token123'
39
+ }
40
+ )
41
+ .to_return(body: { name: 'Steve' }.to_json)
42
+
43
+ DummyCustomer.find(1)
44
+
45
+ expect(customer_request).to have_been_requested
46
+ end
20
47
  end
21
48
  end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ApplicationController < ActionController::Base
4
+ include LHS::OAuth
5
+ ACCESS_TOKEN = 'token-12345'.freeze
6
+
4
7
  # Prevent CSRF attacks by raising an exception.
5
8
  # For APIs, you may want to use :null_session instead.
6
9
  protect_from_forgery with: :exception
@@ -8,4 +11,8 @@ class ApplicationController < ActionController::Base
8
11
  def root
9
12
  render nothing: true
10
13
  end
14
+
15
+ def access_token
16
+ ACCESS_TOKEN
17
+ end
11
18
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AutomaticAuthenticationController < ApplicationController
4
+
5
+ def o_auth
6
+ render json: {
7
+ record: DummyRecordWithOauth.find(1).as_json,
8
+ records: DummyRecordWithOauth.where(color: 'blue').as_json
9
+ }
10
+ end
11
+ end
@@ -5,7 +5,7 @@ class ErrorHandlingWithChainsController < ApplicationController
5
5
  # Example where the query chain is resolved
6
6
  # in the view (during render 'show')
7
7
  def fetch_in_view
8
- @records = Record
8
+ @records = DummyRecord
9
9
  .handle(LHC::Error, ->(error) { handle_error(error) })
10
10
  .where(color: 'blue')
11
11
  render 'show'
@@ -15,7 +15,7 @@ class ErrorHandlingWithChainsController < ApplicationController
15
15
  # Example where the query chain is resolved
16
16
  # before the view is rendered
17
17
  def fetch_in_controller
18
- @records = Record
18
+ @records = DummyRecord
19
19
  .handle(LHC::Error, ->(error) { handle_error(error) })
20
20
  .where(color: 'blue').fetch
21
21
  render 'show'
@@ -3,8 +3,8 @@
3
3
  class ExtendedRollbarController < ApplicationController
4
4
 
5
5
  def extended_rollbar
6
- Record.where(color: 'blue').fetch
7
- Record.where(color: 'red').fetch
6
+ DummyRecord.where(color: 'blue').fetch
7
+ DummyRecord.where(color: 'red').fetch
8
8
  raise "Let's see if rollbar logs information about what kind of requests where made around here!"
9
9
  end
10
10
  end
@@ -4,12 +4,12 @@ class OptionBlocksController < ApplicationController
4
4
 
5
5
  def first
6
6
  LHS::OptionBlocks::CurrentOptionBlock.options = { params: { request: 'first' } }
7
- Record.where(request: 'second').fetch
7
+ DummyRecord.where(request: 'second').fetch
8
8
  render text: 'ok'
9
9
  end
10
10
 
11
11
  def second
12
- Record.where(request: 'second').fetch
12
+ DummyRecord.where(request: 'second').fetch
13
13
  render text: 'ok'
14
14
  end
15
15
  end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyCustomer < Providers::CustomerSystem
4
+ endpoint 'http://customers'
5
+ endpoint 'http://customers/{id}'
6
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Record < LHS::Record
3
+ class DummyRecord < LHS::Record
4
4
  endpoint 'http://datastore/v2/records'
5
5
  endpoint 'http://datastore/v2/records/{id}'
6
6
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DummyRecordWithOauth < LHS::Record
4
+ oauth
5
+ endpoint 'http://datastore/v2/records_with_oauth'
6
+ endpoint 'http://datastore/v2/records_with_oauth/{id}'
7
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class User < LHS::Record
3
+ class DummyUser < LHS::Record
4
4
  endpoint 'http://datastore/v2/users'
5
5
  endpoint 'http://datastore/v2/users/{id}'
6
6
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Providers
4
+ class CustomerSystem < LHS::Record
5
+ provider(headers: { 'Authorization': 'token123' })
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ LHS.configure do |config|
2
+ config.auto_oauth = -> { access_token }
3
+ end
@@ -3,6 +3,9 @@
3
3
  Rails.application.routes.draw do
4
4
  root 'application#root'
5
5
 
6
+ # Automatic Authentication
7
+ get 'automatic_authentication/oauth' => 'automatic_authentication#o_auth'
8
+
6
9
  # Request Cycle Cache
7
10
  get 'request_cycle_cache/simple' => 'request_cycle_cache#simple'
8
11
  get 'request_cycle_cache/no_caching_interceptor' => 'request_cycle_cache#no_caching_interceptor'
@@ -13,7 +13,8 @@ describe 'Option Blocks', type: :request do
13
13
  .to_return(status: 200)
14
14
  end
15
15
 
16
- it 'always ensures option blocks are always reset for new requests', dummy_models: true do
16
+ it 'always ensures option blocks are always reset for new requests',
17
+ dummy_models: true, reset_before: true do
17
18
  get '/option_blocks/first'
18
19
  expect(first_request).to have_been_made.once
19
20
  get '/option_blocks/second'
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails_helper'
4
- require 'lhc/test/cache_helper.rb'
4
+ require 'lhc/rspec'
5
5
 
6
6
  describe 'Error handling with chains', type: :request do
7
7
  let!(:request) do
@@ -664,5 +664,64 @@ describe LHS::Record do
664
664
  expect(nested_request).to have_been_requested
665
665
  expect(place.customer.salesforce.name).to eq 'Steve'
666
666
  end
667
+
668
+ context 'included data has a configured record endpoint option' do
669
+ before do
670
+ class SalesforceCustomer < LHS::Record
671
+ endpoint 'https://salesforce/customers/{id}', headers: { 'Authorization': 'Bearer 123' }
672
+ end
673
+ end
674
+
675
+ let!(:nested_request) do
676
+ stub_request(:get, "https://salesforce/customers/1")
677
+ .with(headers: { 'Authorization' => 'Bearer 123' })
678
+ .to_return(body: {
679
+ name: 'Steve'
680
+ }.to_json)
681
+ end
682
+
683
+ it 'includes data that has been nested in an additional structure' do
684
+ place = Place.includes(customer: :salesforce).find(1)
685
+ expect(nested_request).to have_been_requested
686
+ expect(place.customer.salesforce.name).to eq 'Steve'
687
+ end
688
+ end
689
+ end
690
+
691
+ context 'include empty structures' do
692
+ before do
693
+ class Place < LHS::Record
694
+ endpoint 'https://places/{id}'
695
+ end
696
+ stub_request(:get, "https://places/1")
697
+ .to_return(body: {
698
+ id: '123'
699
+ }.to_json)
700
+ end
701
+
702
+ it 'skips includes when there is nothing and also does not raise an exception' do
703
+ expect(-> {
704
+ Place.includes(contracts: :product).find(1)
705
+ }).not_to raise_exception
706
+ end
707
+ end
708
+
709
+ context 'include partially empty structures' do
710
+ before do
711
+ class Place < LHS::Record
712
+ endpoint 'https://places/{id}'
713
+ end
714
+ stub_request(:get, "https://places/1")
715
+ .to_return(body: {
716
+ id: '123',
717
+ customer: {}
718
+ }.to_json)
719
+ end
720
+
721
+ it 'skips includes when there is nothing and also does not raise an exception' do
722
+ expect(-> {
723
+ Place.includes(customer: :salesforce).find(1)
724
+ }).not_to raise_exception
725
+ end
667
726
  end
668
727
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails_helper'
4
- require 'lhc/test/cache_helper.rb'
4
+ require 'lhc/rspec'
5
5
 
6
6
  describe 'Request Cycle Cache', type: :request do
7
7
  let!(:request) do
@@ -37,7 +37,7 @@ describe 'Request Cycle Cache', type: :request do
37
37
  expect(lambda do
38
38
  get '/request_cycle_cache/no_caching_interceptor'
39
39
  end).to output(
40
- %r{\[WARNING\] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured \(see https://github.com/local-ch/lhc/blob/master/docs/interceptors/caching.md#caching-interceptor\)!}
40
+ %r{\[WARNING\] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured \(see https://github.com/local-ch/lhc/blob/master/README.md#caching-interceptor\)!}
41
41
  ).to_stderr
42
42
  expect(request).to have_been_made.times(2)
43
43
  end
@@ -70,7 +70,7 @@ describe 'Request Cycle Cache', type: :request do
70
70
  expect(lambda do
71
71
  get '/request_cycle_cache/no_caching_interceptor'
72
72
  end).not_to output(
73
- %r{\[WARNING\] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured \(see https://github.com/local-ch/lhc/blob/master/docs/interceptors/caching.md#caching-interceptor\)!}
73
+ %r{\[WARNING\] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured \(see https://github.com/local-ch/lhc/blob/master/README.md#caching-interceptor\)!}
74
74
  ).to_stderr
75
75
  expect(request).to have_been_made.times(2)
76
76
  end
@@ -12,10 +12,15 @@ end
12
12
 
13
13
  class LHS::Record
14
14
 
15
- CHILDREN = []
15
+ DESCENDANTS = []
16
16
 
17
17
  def self.inherited(child)
18
- CHILDREN.push(child)
18
+ DESCENDANTS.push(child)
19
+ child.singleton_class.class_eval do
20
+ define_method(:inherited) do |grand_child|
21
+ DESCENDANTS.push(grand_child)
22
+ end
23
+ end
19
24
  super
20
25
  end
21
26
 
@@ -27,9 +32,28 @@ end
27
32
 
28
33
  def reset_lhs
29
34
  LHS::Record::Endpoints.all = {}
30
- LHS::Record::CHILDREN.each do |child|
31
- child.endpoints = [] if !child.name['LHS'] && defined?(child.endpoints)
32
- child.configuration({}) if !child.name['LHS']
35
+ LHS::Record::DESCENDANTS.each do |decendant|
36
+ decendant.endpoints = [] if !decendant.name['LHS'] && defined?(decendant.endpoints)
37
+ decendant.configuration({}) if !decendant.name['LHS']
38
+ end
39
+ end
40
+
41
+ def model_files_to_reload
42
+ Dir.glob(Rails.root.join('app', 'models', '**', '*.rb'))
43
+ end
44
+
45
+ def reload_direct_inheritance
46
+ model_files_to_reload.map do |file|
47
+ next unless File.read(file).match('LHS::Record')
48
+ load file
49
+ file.split('models/').last.gsub('.rb', '').classify
50
+ end.compact
51
+ end
52
+
53
+ def reload_inheriting_records(parents)
54
+ model_files_to_reload.each do |file|
55
+ next if parents.none? { |parent| File.read(file).match(parent) }
56
+ load file
33
57
  end
34
58
  end
35
59
 
@@ -37,9 +61,7 @@ RSpec.configure do |config|
37
61
  config.before do |spec|
38
62
  reset_lhc unless spec.metadata.key?(:reset_before) && spec.metadata[:reset_before] == false
39
63
  reset_lhs unless spec.metadata.key?(:reset_before) && spec.metadata[:reset_before] == false
40
- next unless spec.metadata.key?(:dummy_models) && spec.metadata[:dummy_models] == true
41
- Dir.glob(Rails.root.join('app', 'models', '**', '*.rb')).each do |file|
42
- load file if File.read(file).match('LHS::Record')
43
- end
64
+ next if !spec.metadata.key?(:dummy_models) || spec.metadata[:dummy_models] != true
65
+ reload_inheriting_records(reload_direct_inheritance)
44
66
  end
45
67
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhs
3
3
  version: !ruby/object:Gem::Version
4
- version: 21.2.1
4
+ version: 21.3.0.pre.autoauth.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhs/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-20 00:00:00.000000000 Z
11
+ date: 2020-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -283,6 +283,7 @@ files:
283
283
  - lib/lhs/concerns/item/save.rb
284
284
  - lib/lhs/concerns/item/update.rb
285
285
  - lib/lhs/concerns/item/validation.rb
286
+ - lib/lhs/concerns/o_auth.rb
286
287
  - lib/lhs/concerns/option_blocks.rb
287
288
  - lib/lhs/concerns/proxy/accessors.rb
288
289
  - lib/lhs/concerns/proxy/create.rb
@@ -314,6 +315,8 @@ files:
314
315
  - lib/lhs/config.rb
315
316
  - lib/lhs/data.rb
316
317
  - lib/lhs/endpoint.rb
318
+ - lib/lhs/interceptors/auto_oauth/interceptor.rb
319
+ - lib/lhs/interceptors/auto_oauth/thread_registry.rb
317
320
  - lib/lhs/interceptors/extended_rollbar/handler.rb
318
321
  - lib/lhs/interceptors/extended_rollbar/interceptor.rb
319
322
  - lib/lhs/interceptors/extended_rollbar/thread_registry.rb
@@ -340,6 +343,7 @@ files:
340
343
  - lib/lhs/version.rb
341
344
  - script/ci/build.sh
342
345
  - spec/.DS_Store
346
+ - spec/auto_oauth_spec.rb
343
347
  - spec/autoloading_spec.rb
344
348
  - spec/collection/accessors_spec.rb
345
349
  - spec/collection/collection_items_spec.rb
@@ -372,6 +376,7 @@ files:
372
376
  - spec/dummy/app/assets/javascripts/application.js
373
377
  - spec/dummy/app/assets/stylesheets/application.css
374
378
  - spec/dummy/app/controllers/application_controller.rb
379
+ - spec/dummy/app/controllers/automatic_authentication_controller.rb
375
380
  - spec/dummy/app/controllers/concerns/.keep
376
381
  - spec/dummy/app/controllers/error_handling_with_chains_controller.rb
377
382
  - spec/dummy/app/controllers/extended_rollbar_controller.rb
@@ -381,8 +386,11 @@ files:
381
386
  - spec/dummy/app/mailers/.keep
382
387
  - spec/dummy/app/models/.keep
383
388
  - spec/dummy/app/models/concerns/.keep
384
- - spec/dummy/app/models/record.rb
385
- - spec/dummy/app/models/user.rb
389
+ - spec/dummy/app/models/dummy_customer.rb
390
+ - spec/dummy/app/models/dummy_record.rb
391
+ - spec/dummy/app/models/dummy_record_with_oauth.rb
392
+ - spec/dummy/app/models/dummy_user.rb
393
+ - spec/dummy/app/models/providers/customer_system.rb
386
394
  - spec/dummy/app/views/error_handling_with_chains/error.html.erb
387
395
  - spec/dummy/app/views/error_handling_with_chains/show.html.erb
388
396
  - spec/dummy/app/views/form_for.html.erb
@@ -402,6 +410,7 @@ files:
402
410
  - spec/dummy/config/initializers/cookies_serializer.rb
403
411
  - spec/dummy/config/initializers/filter_parameter_logging.rb
404
412
  - spec/dummy/config/initializers/inflections.rb
413
+ - spec/dummy/config/initializers/lhs.rb
405
414
  - spec/dummy/config/initializers/mime_types.rb
406
415
  - spec/dummy/config/initializers/rollbar.rb
407
416
  - spec/dummy/config/initializers/session_store.rb
@@ -546,9 +555,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
546
555
  version: 2.3.0
547
556
  required_rubygems_version: !ruby/object:Gem::Requirement
548
557
  requirements:
549
- - - ">="
558
+ - - ">"
550
559
  - !ruby/object:Gem::Version
551
- version: '0'
560
+ version: 1.3.1
552
561
  requirements:
553
562
  - Ruby >= 2.3.0
554
563
  rubygems_version: 3.0.6
@@ -557,6 +566,7 @@ specification_version: 4
557
566
  summary: 'REST services accelerator: Rails gem providing an easy, active-record-like
558
567
  interface for http (hypermedia) json services'
559
568
  test_files:
569
+ - spec/auto_oauth_spec.rb
560
570
  - spec/autoloading_spec.rb
561
571
  - spec/collection/accessors_spec.rb
562
572
  - spec/collection/collection_items_spec.rb
@@ -589,6 +599,7 @@ test_files:
589
599
  - spec/dummy/app/assets/javascripts/application.js
590
600
  - spec/dummy/app/assets/stylesheets/application.css
591
601
  - spec/dummy/app/controllers/application_controller.rb
602
+ - spec/dummy/app/controllers/automatic_authentication_controller.rb
592
603
  - spec/dummy/app/controllers/concerns/.keep
593
604
  - spec/dummy/app/controllers/error_handling_with_chains_controller.rb
594
605
  - spec/dummy/app/controllers/extended_rollbar_controller.rb
@@ -598,8 +609,11 @@ test_files:
598
609
  - spec/dummy/app/mailers/.keep
599
610
  - spec/dummy/app/models/.keep
600
611
  - spec/dummy/app/models/concerns/.keep
601
- - spec/dummy/app/models/record.rb
602
- - spec/dummy/app/models/user.rb
612
+ - spec/dummy/app/models/dummy_customer.rb
613
+ - spec/dummy/app/models/dummy_record.rb
614
+ - spec/dummy/app/models/dummy_record_with_oauth.rb
615
+ - spec/dummy/app/models/dummy_user.rb
616
+ - spec/dummy/app/models/providers/customer_system.rb
603
617
  - spec/dummy/app/views/error_handling_with_chains/error.html.erb
604
618
  - spec/dummy/app/views/error_handling_with_chains/show.html.erb
605
619
  - spec/dummy/app/views/form_for.html.erb
@@ -619,6 +633,7 @@ test_files:
619
633
  - spec/dummy/config/initializers/cookies_serializer.rb
620
634
  - spec/dummy/config/initializers/filter_parameter_logging.rb
621
635
  - spec/dummy/config/initializers/inflections.rb
636
+ - spec/dummy/config/initializers/lhs.rb
622
637
  - spec/dummy/config/initializers/mime_types.rb
623
638
  - spec/dummy/config/initializers/rollbar.rb
624
639
  - spec/dummy/config/initializers/session_store.rb