openstax_utilities 4.1.0 → 4.3.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 (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