openstax_utilities 4.1.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +5 -5
  2. data/app/routines/openstax/utilities/limit_and_paginate_relation.rb +85 -0
  3. data/app/routines/openstax/utilities/order_relation.rb +105 -0
  4. data/app/routines/openstax/utilities/search_and_organize_relation.rb +130 -0
  5. data/app/routines/openstax/utilities/search_relation.rb +94 -0
  6. data/lib/openstax/utilities/access_policy.rb +4 -3
  7. data/lib/openstax/utilities/assets.rb +29 -0
  8. data/lib/openstax/utilities/assets/manifest.rb +62 -0
  9. data/lib/openstax/utilities/version.rb +1 -1
  10. data/lib/openstax_utilities.rb +5 -5
  11. data/spec/cassettes/OpenStax_Utilities_Assets/loading_remote_manifest/uses_remote_json.yml +353 -0
  12. data/spec/dummy/app/access_policies/dummier_access_policy.rb +10 -0
  13. data/spec/dummy/app/assets/config/manifest.js +3 -0
  14. data/spec/dummy/config/application.rb +6 -11
  15. data/spec/dummy/config/environments/test.rb +1 -1
  16. data/spec/dummy/config/initializers/search_users.rb +26 -0
  17. data/spec/dummy/config/secrets.yml +2 -5
  18. data/spec/dummy/db/test.sqlite3 +0 -0
  19. data/spec/dummy/log/test.log +32144 -0
  20. data/spec/dummy/tmp/cache/C09/760/6da7b2a29da9cb0f80ef102c7effb91fab3374db +0 -0
  21. data/spec/factories/user.rb +1 -1
  22. data/spec/lib/openstax/utilities/access_policy_spec.rb +16 -15
  23. data/spec/lib/openstax/utilities/assets_spec.rb +40 -0
  24. data/spec/rails_helper.rb +1 -2
  25. data/spec/routines/openstax/utilities/limit_and_paginate_relation_spec.rb +72 -0
  26. data/spec/routines/openstax/utilities/order_relation_spec.rb +55 -0
  27. data/spec/routines/openstax/utilities/{abstract_keyword_search_routine_spec.rb → search_and_organize_relation_spec.rb} +54 -47
  28. data/spec/routines/openstax/utilities/search_relation_spec.rb +81 -0
  29. data/spec/vcr_helper.rb +18 -0
  30. metadata +123 -44
  31. data/app/handlers/openstax/utilities/keyword_search_handler.rb +0 -95
  32. data/app/routines/openstax/utilities/abstract_keyword_search_routine.rb +0 -158
  33. data/spec/dummy/app/routines/search_users.rb +0 -21
  34. data/spec/handlers/openstax/utilities/keyword_search_handler_spec.rb +0 -126
@@ -1,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :user do
3
3
  username { SecureRandom.hex.to_s }
4
4
  password_hash { SecureRandom.hex.to_s }
@@ -5,7 +5,7 @@ module OpenStax
5
5
 
6
6
  describe AccessPolicy do
7
7
 
8
- let!(:user) { FactoryGirl.create :user }
8
+ let!(:user) { FactoryBot.create :user }
9
9
 
10
10
  it 'responds to any _allowed? calls' do
11
11
  AccessPolicy.register(User, DummyAccessPolicy)
@@ -24,30 +24,31 @@ module OpenStax
24
24
 
25
25
  it 'delegates checks to policy classes based on resource class' do
26
26
  dummy_object = double('Dummy')
27
- dummy_policy = double('Dummy Policy', :action_allowed? => true)
28
27
 
29
28
  AccessPolicy.register(User, DummyAccessPolicy)
30
- AccessPolicy.register(dummy_object.class, dummy_policy)
29
+ AccessPolicy.register(dummy_object.class, DummierAccessPolicy)
31
30
 
32
31
  DummyAccessPolicy.last_action = nil
33
32
  DummyAccessPolicy.last_requestor = nil
34
33
  DummyAccessPolicy.last_resource = nil
35
34
 
36
- expect(AccessPolicy.action_allowed?(:read, user, dummy_object)).to(
37
- eq(true))
38
-
39
- expect{AccessPolicy.require_action_allowed!(:read, user, dummy_object)
40
- }.not_to raise_error
35
+ DummierAccessPolicy.last_action = nil
36
+ DummierAccessPolicy.last_requestor = nil
37
+ DummierAccessPolicy.last_resource = nil
41
38
 
42
- expect(DummyAccessPolicy.last_action).to be_nil
43
- expect(DummyAccessPolicy.last_requestor).to be_nil
44
- expect(DummyAccessPolicy.last_resource).to be_nil
39
+ expect(AccessPolicy.action_allowed?(:read, user, dummy_object)).to eq true
40
+ expect{
41
+ AccessPolicy.require_action_allowed!(:read, user, dummy_object)
42
+ }.not_to raise_error
45
43
 
46
- expect(AccessPolicy.action_allowed?(:create, user, User.new)).to(
47
- eq(true))
44
+ expect(DummierAccessPolicy.last_action).to eq(:read)
45
+ expect(DummierAccessPolicy.last_requestor).to eq(user)
46
+ expect(DummierAccessPolicy.last_resource).to eq(dummy_object)
48
47
 
49
- expect{AccessPolicy.require_action_allowed!(:create, user, User.new)
50
- }.not_to raise_error
48
+ expect(AccessPolicy.action_allowed?(:create, user, User.new)).to eq true
49
+ expect{
50
+ AccessPolicy.require_action_allowed!(:create, user, User.new)
51
+ }.not_to raise_error
51
52
 
52
53
  expect(DummyAccessPolicy.last_action).to eq(:create)
53
54
  expect(DummyAccessPolicy.last_requestor).to eq(user)
@@ -0,0 +1,40 @@
1
+ require 'rails_helper'
2
+ require 'vcr_helper'
3
+
4
+ RSpec.describe OpenStax::Utilities::Assets, vcr: VCR_OPTS do
5
+ before { RequestStore.store[:assets_manifest] = nil }
6
+
7
+ it 'defaults to name.js when manifest is missing' do
8
+ expect_any_instance_of(Faraday::Connection).to receive(:get).and_return(
9
+ OpenStruct.new success?: false
10
+ )
11
+ expect(described_class.tags_for(:foo)).to include "src='http://localhost:8000/dist/foo.js'"
12
+ end
13
+
14
+ it 'reads asset url from manifest' do
15
+ expect_any_instance_of(Faraday::Connection).to receive(:get).and_return(
16
+ OpenStruct.new(
17
+ success?: true,
18
+ body: { entrypoints: { foo: { js: [ 'foo-732c56c32ff399b62.min.bar' ] } } }.to_json
19
+ )
20
+ )
21
+ expect(described_class.tags_for(:foo)).to include(
22
+ "src='http://localhost:8000/dist/foo-732c56c32ff399b62.min.bar'"
23
+ )
24
+ end
25
+
26
+ context 'loading remote manifest' do
27
+ before do
28
+ @previous_assets_url = Rails.application.secrets.assets_url
29
+ Rails.application.secrets.assets_url = 'https://tutor-dev.openstax.org/assets'
30
+ end
31
+ after { Rails.application.secrets.assets_url = @previous_assets_url }
32
+
33
+ it 'uses remote json' do
34
+ expect(described_class.manifest).to be_kind_of described_class::Manifest
35
+ expect(described_class.tags_for(:tutor)).to(
36
+ eq "<script type='text/javascript' src='https://tutor-dev.openstax.org/assets/tutor-b920eb0be760a7c440bf.min.js' crossorigin='anonymous' async></script>"
37
+ )
38
+ end
39
+ end
40
+ end
@@ -3,9 +3,8 @@ ENV["RAILS_ENV"] ||= 'test'
3
3
  require 'spec_helper'
4
4
  require File.expand_path("../dummy/config/environment", __FILE__)
5
5
  require 'rspec/rails'
6
- require 'factory_girl_rails'
6
+ require 'factory_bot_rails'
7
7
  require 'faker'
8
- require 'squeel'
9
8
 
10
9
  # Add additional requires below this line. Rails is not loaded until this point!
11
10
 
@@ -0,0 +1,72 @@
1
+ require 'rails_helper'
2
+
3
+ module OpenStax
4
+ module Utilities
5
+ describe SearchRelation do
6
+
7
+ let!(:john_doe) { FactoryBot.create :user, name: "John Doe",
8
+ username: "doejohn",
9
+ email: "john@doe.com" }
10
+
11
+ let!(:jane_doe) { FactoryBot.create :user, name: "Jane Doe",
12
+ username: "doejane",
13
+ email: "jane@doe.com" }
14
+
15
+ let!(:jack_doe) { FactoryBot.create :user, name: "Jack Doe",
16
+ username: "doejack",
17
+ email: "jack@doe.com" }
18
+
19
+ before(:each) do
20
+ 100.times do
21
+ FactoryBot.create(:user)
22
+ end
23
+
24
+ @relation = User.unscoped
25
+ end
26
+
27
+ it "returns nothing if too many results" do
28
+ routine = LimitAndPaginateRelation.call(relation: @relation,
29
+ max_items: 10)
30
+ outputs = routine.outputs
31
+ errors = routine.errors
32
+ expect(outputs).not_to be_empty
33
+ expect(outputs[:total_count]).to eq User.count
34
+ expect(outputs[:items]).to be_empty
35
+ expect(errors).not_to be_empty
36
+ expect(errors.first.code).to eq :too_many_items
37
+ end
38
+
39
+ it "paginates results" do
40
+ all_items = @relation.to_a
41
+
42
+ items = LimitAndPaginateRelation.call(relation: @relation,
43
+ per_page: 20).outputs[:items]
44
+ expect(items.limit(nil).offset(nil).count).to eq all_items.count
45
+ expect(items.limit(nil).offset(nil).to_a).to eq all_items
46
+ expect(items.count).to eq 20
47
+ expect(items.to_a).to eq all_items[0..19]
48
+
49
+ for page in 1..5
50
+ items = LimitAndPaginateRelation.call(relation: @relation,
51
+ page: page,
52
+ per_page: 20)
53
+ .outputs[:items]
54
+ expect(items.limit(nil).offset(nil).count).to eq all_items.count
55
+ expect(items.limit(nil).offset(nil).to_a).to eq all_items
56
+ expect(items.count).to eq 20
57
+ expect(items.to_a).to eq all_items.slice(20*(page-1), 20)
58
+ end
59
+
60
+ items = LimitAndPaginateRelation.call(relation: @relation,
61
+ page: 1000,
62
+ per_page: 20)
63
+ .outputs[:items]
64
+ expect(items.limit(nil).offset(nil).count).to eq all_items.count
65
+ expect(items.limit(nil).offset(nil).to_a).to eq all_items
66
+ expect(items.count).to eq 0
67
+ expect(items.to_a).to be_empty
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,55 @@
1
+ require 'rails_helper'
2
+
3
+ module OpenStax
4
+ module Utilities
5
+ describe SearchRelation do
6
+ let!(:john_doe) do
7
+ FactoryBot.create :user, name: "John Doe", username: "doejohn", email: "john@doe.com"
8
+ end
9
+
10
+ let!(:jane_doe) do
11
+ FactoryBot.create :user, name: "Jane Doe", username: "doejane", email: "jane@doe.com"
12
+ end
13
+
14
+ let!(:jack_doe) do
15
+ FactoryBot.create :user, name: "Jack Doe", username: "doejack", email: "jack@doe.com"
16
+ end
17
+
18
+ before(:each) do
19
+ 100.times { FactoryBot.create(:user) }
20
+
21
+ @relation = User.where(User.arel_table[:username].matches('doe%'))
22
+ end
23
+
24
+ it "orders results by multiple fields in different directions" do
25
+ items = OrderRelation.call(
26
+ relation: @relation,
27
+ sortable_fields: SearchUsers::SORTABLE_FIELDS,
28
+ order_by: 'cReAtEd_At AsC, iD'
29
+ ).outputs[:items]
30
+ expect(items).to include(john_doe)
31
+ expect(items).to include(jane_doe)
32
+ expect(items).to include(jack_doe)
33
+ john_index = items.index(john_doe)
34
+ jane_index = items.index(jane_doe)
35
+ jack_index = items.index(jack_doe)
36
+ expect(jane_index).to be > john_index
37
+ expect(jack_index).to be > jane_index
38
+
39
+ items = OrderRelation.call(
40
+ relation: @relation,
41
+ sortable_fields: SearchUsers::SORTABLE_FIELDS,
42
+ order_by: 'CrEaTeD_aT dEsC, Id DeSc'
43
+ ).outputs[:items]
44
+ expect(items).to include(john_doe)
45
+ expect(items).to include(jane_doe)
46
+ expect(items).to include(jack_doe)
47
+ john_index = items.index(john_doe)
48
+ jane_index = items.index(jane_doe)
49
+ jack_index = items.index(jack_doe)
50
+ expect(jane_index).to be < john_index
51
+ expect(jack_index).to be < jane_index
52
+ end
53
+ end
54
+ end
55
+ end
@@ -2,28 +2,36 @@ require 'rails_helper'
2
2
 
3
3
  module OpenStax
4
4
  module Utilities
5
- describe AbstractKeywordSearchRoutine do
5
+ describe SearchAndOrganizeRelation do
6
6
 
7
- let!(:john_doe) { FactoryGirl.create :user, name: "John Doe",
7
+ OPTIONS = {
8
+ relation: SearchUsers::RELATION,
9
+ search_proc: SearchUsers::SEARCH_PROC,
10
+ sortable_fields: SearchUsers::SORTABLE_FIELDS,
11
+ max_items: SearchUsers::MAX_ITEMS
12
+ }
13
+
14
+ let!(:john_doe) { FactoryBot.create :user, name: "John Doe",
8
15
  username: "doejohn",
9
16
  email: "john@doe.com" }
10
17
 
11
- let!(:jane_doe) { FactoryGirl.create :user, name: "Jane Doe",
18
+ let!(:jane_doe) { FactoryBot.create :user, name: "Jane Doe",
12
19
  username: "doejane",
13
20
  email: "jane@doe.com" }
14
21
 
15
- let!(:jack_doe) { FactoryGirl.create :user, name: "Jack Doe",
22
+ let!(:jack_doe) { FactoryBot.create :user, name: "Jack Doe",
16
23
  username: "doejack",
17
24
  email: "jack@doe.com" }
18
25
 
19
26
  before(:each) do
20
27
  100.times do
21
- FactoryGirl.create(:user)
28
+ FactoryBot.create(:user)
22
29
  end
23
30
  end
24
31
 
25
- it "filters results based on one field" do
26
- items = SearchUsers.call(User.unscoped, 'last_name:dOe').outputs[:items]
32
+ it "filters results" do
33
+ items = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
34
+ q: 'last_name:dOe'})).outputs[:items]
27
35
 
28
36
  expect(items).to include(john_doe)
29
37
  expect(items).to include(jane_doe)
@@ -31,11 +39,9 @@ module OpenStax
31
39
  items.each do |item|
32
40
  expect(item.name.downcase).to match(/\A[\w]* doe[\w]*\z/i)
33
41
  end
34
- end
35
42
 
36
- it "filters results based on multiple fields" do
37
- items = SearchUsers.call(User.unscoped, 'first_name:jOhN last_name:DoE')
38
- .outputs[:items]
43
+ items = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
44
+ q: 'first_name:jOhN last_name:DoE'})).outputs[:items]
39
45
 
40
46
  expect(items).to include(john_doe)
41
47
  expect(items).not_to include(jane_doe)
@@ -43,11 +49,9 @@ module OpenStax
43
49
  items.each do |item|
44
50
  expect(item.name).to match(/\Ajohn[\w]* doe[\w]*\z/i)
45
51
  end
46
- end
47
52
 
48
- it "filters results based on multiple keywords per field" do
49
- items = SearchUsers.call(User.unscoped, 'first_name:JoHn,JaNe last_name:dOe')
50
- .outputs[:items]
53
+ items = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
54
+ q: 'first_name:JoHn,JaNe last_name:dOe'})).outputs[:items]
51
55
 
52
56
  expect(items).to include(john_doe)
53
57
  expect(items).to include(jane_doe)
@@ -57,22 +61,10 @@ module OpenStax
57
61
  end
58
62
  end
59
63
 
60
- it "filters scoped results" do
61
- items = SearchUsers.call(User.where{name.like 'jOhN%'},
62
- 'last_name:dOe').outputs[:items]
63
-
64
- expect(items).to include(john_doe)
65
- expect(items).not_to include(jane_doe)
66
- expect(items).not_to include(jack_doe)
67
- items.each do |item|
68
- expect(item.name.downcase).to match(/\Ajohn[\w]* doe[\w]*\z/i)
69
- end
70
- end
71
-
72
- it "orders results by multiple fields in different directions" do
73
- items = SearchUsers.call(User.unscoped, 'username:DoE',
74
- order_by: 'cReAtEd_At AsC, iD')
75
- .outputs[:items]
64
+ it "orders results" do
65
+ items = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
66
+ order_by: 'cReAtEd_At AsC, iD',
67
+ q: 'username:dOe'})).outputs[:items].to_a
76
68
  expect(items).to include(john_doe)
77
69
  expect(items).to include(jane_doe)
78
70
  expect(items).to include(jack_doe)
@@ -81,13 +73,10 @@ module OpenStax
81
73
  jack_index = items.index(jack_doe)
82
74
  expect(jane_index).to be > john_index
83
75
  expect(jack_index).to be > jane_index
84
- items.each do |item|
85
- expect(item.username).to match(/\Adoe[\w]*\z/i)
86
- end
87
76
 
88
- items = SearchUsers.call(User.unscoped, 'username:dOe',
89
- order_by: 'CrEaTeD_aT dEsC, Id DeSc')
90
- .outputs[:items]
77
+ items = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
78
+ order_by: 'CrEaTeD_aT dEsC, Id DeSc',
79
+ q: 'username:dOe'})).outputs[:items].to_a
91
80
  expect(items).to include(john_doe)
92
81
  expect(items).to include(jane_doe)
93
82
  expect(items).to include(jack_doe)
@@ -96,31 +85,49 @@ module OpenStax
96
85
  jack_index = items.index(jack_doe)
97
86
  expect(jane_index).to be < john_index
98
87
  expect(jack_index).to be < jane_index
99
- items.each do |item|
100
- expect(item.username).to match(/\Adoe[\w]*\z/i)
101
- end
88
+ end
89
+
90
+ it "returns nothing if too many results" do
91
+ routine = SearchAndOrganizeRelation.call(OPTIONS.merge(params: {
92
+ q: ''}))
93
+ outputs = routine.outputs
94
+ errors = routine.errors
95
+ expect(outputs).not_to be_empty
96
+ expect(outputs[:total_count]).to eq User.count
97
+ expect(outputs[:items]).to be_empty
98
+ expect(errors).not_to be_empty
99
+ expect(errors.first.code).to eq :too_many_items
102
100
  end
103
101
 
104
102
  it "paginates results" do
105
- all_items = SearchUsers.call(User.unscoped, '').outputs[:items].to_a
103
+ all_items = SearchUsers::RELATION.to_a
106
104
 
107
- items = SearchUsers.call(User.unscoped, '', per_page: 20).outputs[:items]
108
- expect(items.limit(nil).offset(nil).count).to eq all_items.count
105
+ items = SearchAndOrganizeRelation.call(OPTIONS
106
+ .except(:max_items)
107
+ .merge(params: {q: '',
108
+ per_page: 20})).outputs[:items]
109
+ expect(items.limit(nil).offset(nil).count).to eq all_items.length
109
110
  expect(items.limit(nil).offset(nil).to_a).to eq all_items
110
111
  expect(items.count).to eq 20
111
112
  expect(items.to_a).to eq all_items[0..19]
112
113
 
113
114
  for page in 1..5
114
- items = SearchUsers.call(User.unscoped, '', page: page, per_page: 20)
115
- .outputs[:items]
115
+ items = SearchAndOrganizeRelation.call(OPTIONS
116
+ .except(:max_items)
117
+ .merge(params: {q: '',
118
+ page: page,
119
+ per_page: 20})).outputs[:items]
116
120
  expect(items.limit(nil).offset(nil).count).to eq all_items.count
117
121
  expect(items.limit(nil).offset(nil).to_a).to eq all_items
118
122
  expect(items.count).to eq 20
119
123
  expect(items.to_a).to eq all_items.slice(20*(page-1), 20)
120
124
  end
121
125
 
122
- items = SearchUsers.call(User.unscoped, '', page: 1000, per_page: 20)
123
- .outputs[:items]
126
+ items = SearchAndOrganizeRelation.call(OPTIONS
127
+ .except(:max_items)
128
+ .merge(params: {q: '',
129
+ page: 1000,
130
+ per_page: 20})).outputs[:items]
124
131
  expect(items.limit(nil).offset(nil).count).to eq all_items.count
125
132
  expect(items.limit(nil).offset(nil).to_a).to eq all_items
126
133
  expect(items.count).to eq 0
@@ -0,0 +1,81 @@
1
+ require 'rails_helper'
2
+
3
+ module OpenStax
4
+ module Utilities
5
+ describe SearchRelation do
6
+ let!(:john_doe) do
7
+ FactoryBot.create :user, name: "John Doe", username: "doejohn", email: "john@doe.com"
8
+ end
9
+
10
+ let!(:jane_doe) do
11
+ FactoryBot.create :user, name: "Jane Doe", username: "doejane", email: "jane@doe.com"
12
+ end
13
+
14
+ let!(:jack_doe) do
15
+ FactoryBot.create :user, name: "Jack Doe", username: "doejack", email: "jack@doe.com"
16
+ end
17
+
18
+ before(:each) do
19
+ 100.times do
20
+ FactoryBot.create(:user)
21
+ end
22
+ end
23
+
24
+ it "filters results based on one field" do
25
+ items = SearchRelation.call(relation: SearchUsers::RELATION,
26
+ search_proc: SearchUsers::SEARCH_PROC,
27
+ query: 'last_name:dOe').outputs[:items]
28
+
29
+ expect(items).to include(john_doe)
30
+ expect(items).to include(jane_doe)
31
+ expect(items).to include(jack_doe)
32
+ items.each do |item|
33
+ expect(item.name.downcase).to match(/\A[\w]* doe[\w]*\z/i)
34
+ end
35
+ end
36
+
37
+ it "filters results based on multiple fields" do
38
+ items = SearchRelation.call(relation: SearchUsers::RELATION,
39
+ search_proc: SearchUsers::SEARCH_PROC,
40
+ query: 'first_name:jOhN last_name:DoE')
41
+ .outputs[:items]
42
+
43
+ expect(items).to include(john_doe)
44
+ expect(items).not_to include(jane_doe)
45
+ expect(items).not_to include(jack_doe)
46
+ items.each do |item|
47
+ expect(item.name).to match(/\Ajohn[\w]* doe[\w]*\z/i)
48
+ end
49
+ end
50
+
51
+ it "filters results based on multiple keywords per field" do
52
+ items = SearchRelation.call(relation: SearchUsers::RELATION,
53
+ search_proc: SearchUsers::SEARCH_PROC,
54
+ query: 'first_name:JoHn,JaNe last_name:dOe')
55
+ .outputs[:items]
56
+
57
+ expect(items).to include(john_doe)
58
+ expect(items).to include(jane_doe)
59
+ expect(items).not_to include(jack_doe)
60
+ items.each do |item|
61
+ expect(item.name).to match(/\A[john|jane][\w]* doe[\w]*\z/i)
62
+ end
63
+ end
64
+
65
+ it "filters scoped results" do
66
+ items = SearchRelation.call(
67
+ relation: User.where(User.arel_table[:name].matches('jOhN%')),
68
+ search_proc: SearchUsers::SEARCH_PROC,
69
+ query: 'last_name:dOe'
70
+ ).outputs[:items]
71
+
72
+ expect(items).to include(john_doe)
73
+ expect(items).not_to include(jane_doe)
74
+ expect(items).not_to include(jack_doe)
75
+ items.each do |item|
76
+ expect(item.name.downcase).to match(/\Ajohn[\w]* doe[\w]*\z/i)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end