Browse Source

Refactoring: Migrate *_assets_test.rb to RSpec

Ryan Lue 6 years ago
parent
commit
ca73fdd9b7

+ 10 - 14
spec/factories/overview.rb

@@ -1,30 +1,28 @@
 FactoryBot.define do
-  sequence :test_factory_name do |n|
-    "Test Overview #{n}"
-  end
-end
-
-FactoryBot.define do
-
   factory :overview do
-    name { generate(:test_factory_name) }
-    prio 1
-    role_ids { [ Role.find_by(name: 'Customer').id, Role.find_by(name: 'Agent').id, Role.find_by(name: 'Admin').id ] }
-    out_of_office true
+    sequence(:name) { |n| "Test Overview #{n}" }
+    prio            { 1 }
+    role_ids        { Role.where(name: %w[Customer Agent Admin]).pluck(:id) }
+    out_of_office   { true }
+    updated_by_id   { 1 }
+    created_by_id   { 1 }
+
     condition do
       {
         'ticket.state_id' => {
           operator: 'is',
-          value:    [ Ticket::State.lookup(name: 'new').id, Ticket::State.lookup(name: 'open').id ],
+          value:    Ticket::State.where(name: %w[new open]).pluck(:id),
         },
       }
     end
+
     order do
       {
         by:        'created_at',
         direction: 'DESC',
       }
     end
+
     view do
       {
         d:                 %w[title customer state created_at],
@@ -33,7 +31,5 @@ FactoryBot.define do
         view_mode_default: 's',
       }
     end
-    updated_by_id 1
-    created_by_id 1
   end
 end

+ 6 - 8
spec/factories/sla.rb

@@ -1,11 +1,11 @@
 FactoryBot.define do
   factory :sla do
-    sequence(:name)     { |n| "SLA #{n}" }
-    first_response_time nil
-    update_time         nil
-    solution_time       nil
-    calendar            { create(:calendar) }
-    condition           do
+    calendar
+    sequence(:name) { |n| "SLA #{n}" }
+    created_by_id   { 1 }
+    updated_by_id   { 1 }
+
+    condition do
       {
         'ticket.state_id' => {
           operator: 'is',
@@ -13,7 +13,5 @@ FactoryBot.define do
         },
       }
     end
-    created_by_id 1
-    updated_by_id 1
   end
 end

+ 5 - 5
spec/factories/trigger.rb

@@ -1,10 +1,10 @@
 FactoryBot.define do
   factory :trigger do
     sequence(:name) { |n| "Test trigger #{n}"  }
-    condition     { { 'ticket.state_id' => { 'operator' => 'is not', 'value' => 4 } } }
-    perform       { { 'ticket.state_id' => { 'value' => 4 } } }
-    active        true
-    created_by_id 1
-    updated_by_id 1
+    condition       { { 'ticket.state_id' => { 'operator' => 'is not', 'value' => 4 } } }
+    perform         { { 'ticket.state_id' => { 'value' => 4 } } }
+    active          { true }
+    created_by_id   { 1 }
+    updated_by_id   { 1 }
   end
 end

+ 97 - 0
spec/models/application_model/can_assets_examples.rb

@@ -0,0 +1,97 @@
+RSpec.shared_examples 'ApplicationModel::CanAssets' do |associations: [], selectors: [], own_attributes: true|
+  subject { create(described_class.name.underscore, updated_by_id: admin.id) }
+  let(:admin) { create(:admin_user) }
+
+  describe '#assets (for supplying model data to front-end framework)' do
+    shared_examples 'own asset attributes' do
+      it 'returns a hash with own asset attributes' do
+        expect(subject.assets({}).dig(described_class.to_app_model))
+          .to include(subject.id => hash_including(subject.attributes_with_association_ids))
+      end
+    end
+
+    include_examples 'own asset attributes' if own_attributes
+
+    describe 'for created_by & updated_by users' do
+      let(:users) { User.where(id: subject.attributes.slice('created_by_id', 'updated_by_id').values) }
+      let(:users_assets) { users.reduce({}) { |assets_hash, user| user.assets(assets_hash) } }
+
+      it 'returns a hash with their asset attributes' do
+        expect(subject.assets({})[:User]).to include(users_assets[:User])
+      end
+    end
+
+    context 'when given a non-empty hash' do
+      let(:hash) { { described_class.to_app_model => { foo: 'bar' } } }
+
+      it 'deep-merges assets into it, in place' do
+        expect { subject.assets(hash) }
+          .to change { hash }
+          .to(hash.deep_merge(subject.assets({})))
+      end
+    end
+
+    Array(associations).each do |association|
+      describe "for ##{association} association" do
+        let(:reflection) { described_class.reflect_on_association(association) }
+
+        shared_examples 'single association' do
+          subject { create(described_class.name.underscore, association => single) }
+          let(:single) { create(reflection.class_name.underscore) }
+
+          it 'returns a hash with its asset attributes' do
+            expect(subject.assets({})).to include(single.assets({}))
+          end
+
+          context 'after association has been modified' do
+            it 'does not use a cached value' do
+              subject.assets({})
+              single.update(updated_by_id: User.last.id)
+
+              expect(subject.assets({}).dig(reflection.klass.to_app_model, single.id))
+                .to include(single.attributes_with_association_ids)
+            end
+          end
+        end
+
+        shared_examples 'collection association' do
+          subject { create(described_class.name.underscore, association => collection) }
+          let(:collection) { create_list(reflection.class_name.underscore, 5) }
+          let(:collection_assets) { collection.reduce({}) { |assets_hash, single| single.assets(assets_hash) } }
+
+          it 'returns a hash with their asset attributes' do
+            expect(subject.assets({})).to include(collection_assets)
+          end
+
+          context 'after association has been modified' do
+            it 'does not use a cached value' do
+              subject.assets({})
+              collection.first.update(updated_by_id: User.last.id)
+
+              expect(subject.assets({}).dig(reflection.klass.to_app_model, collection.first.id))
+                .to include(collection.first.attributes_with_association_ids)
+            end
+          end
+        end
+
+        if described_class.reflect_on_association(association).macro.in?(%i[has_one belongs_to])
+          include_examples 'single association'
+        else
+          include_examples 'collection association'
+        end
+      end
+    end
+
+    Array(selectors).each do |s|
+      subject { create(described_class.name.underscore, s => selector) }
+      let(:selector) { { 'ticket.priority_id' => { operator: 'is', value: [1, 2] } } }
+      let(:priorities_assets) { Ticket::Priority.first(2).reduce({}) { |asset_hash, priority| priority.assets(asset_hash) } }
+
+      describe "for objects referenced in ##{s}" do
+        it 'returns a hash with their asset attributes' do
+          expect(subject.assets({})).to include(priorities_assets)
+        end
+      end
+    end
+  end
+end

+ 29 - 0
spec/models/application_model/can_associations_examples.rb

@@ -0,0 +1,29 @@
+RSpec.shared_examples 'ApplicationModel::CanAssociations' do
+  subject { create(described_class.name.underscore) }
+
+  describe '#attributes_with_association_ids (for supplying model data to front-end framework)' do
+    describe 'caching' do
+      let(:cache_key) { "#{described_class}::aws::#{subject.id}" }
+
+      context 'with empty cache' do
+        it 'stores the computed value in the cache' do
+          expect { subject.attributes_with_association_ids }
+            .to change { Rails.cache.read(cache_key) }
+        end
+      end
+
+      context 'with stored value in cache' do
+        before { Rails.cache.write(cache_key, { foo: 'bar' }) }
+
+        it 'returns the cached value' do
+          expect(subject.attributes_with_association_ids).to include(foo: 'bar')
+        end
+
+        it 'does not modify the cached value' do
+          expect { subject.attributes_with_association_ids }
+            .not_to change { Rails.cache.read(cache_key) }
+        end
+      end
+    end
+  end
+end

+ 5 - 1
spec/models/application_model_examples.rb

@@ -1,5 +1,9 @@
+require 'models/application_model/can_assets_examples'
+require 'models/application_model/can_associations_examples'
 require 'models/application_model/checks_import_examples'
 
-RSpec.shared_examples 'ApplicationModel' do
+RSpec.shared_examples 'ApplicationModel' do |options = {}|
+  include_examples 'ApplicationModel::CanAssets', options[:can_assets]
+  include_examples 'ApplicationModel::CanAssociations'
   include_examples 'ApplicationModel::ChecksImport'
 end

+ 1 - 1
spec/models/history_spec.rb

@@ -3,6 +3,6 @@ require 'models/application_model_examples'
 require 'models/concerns/can_be_imported_examples'
 
 RSpec.describe History, type: :model do
-  it_behaves_like 'ApplicationModel'
+  it_behaves_like 'ApplicationModel', can_assets: { own_attributes: false }
   it_behaves_like 'CanBeImported'
 end

+ 6 - 0
spec/models/job_spec.rb

@@ -0,0 +1,6 @@
+require 'rails_helper'
+require 'models/application_model_examples'
+
+RSpec.describe Job, type: :model do
+  it_behaves_like 'ApplicationModel', can_assets: { selectors: %i[condition perform] }
+end

+ 2 - 0
spec/models/organization_spec.rb

@@ -1,9 +1,11 @@
 require 'rails_helper'
+require 'models/application_model_examples'
 require 'models/concerns/can_lookup_examples'
 require 'models/concerns/has_search_index_backend_examples'
 require 'models/concerns/has_xss_sanitized_note_examples'
 
 RSpec.describe Organization, type: :model do
+  it_behaves_like 'ApplicationModel', can_assets: { associations: :members }
   it_behaves_like 'CanLookup'
   it_behaves_like 'HasSearchIndexBackend', indexed_factory: :organization
   it_behaves_like 'HasXssSanitizedNote', model_factory: :organization

+ 2 - 1
spec/models/overview_spec.rb

@@ -1,6 +1,7 @@
 require 'rails_helper'
 
-RSpec.describe Overview do
+RSpec.describe Overview, type: :model do
+  it_behaves_like 'ApplicationModel', can_assets: { associations: :users, selectors: :condition }
 
   context 'link generation' do
 

Some files were not shown because too many files changed in this diff