paper_trail_manager 0.7.0 → 0.8.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +13 -47
  3. data/.gitignore +2 -2
  4. data/.ruby-version +1 -0
  5. data/Appraisals +9 -17
  6. data/CHANGES.md +18 -0
  7. data/README.md +130 -66
  8. data/Rakefile +5 -3
  9. data/app/controllers/paper_trail_manager/changes_controller.rb +16 -1
  10. data/app/helpers/paper_trail_manager/changes_helper.rb +2 -2
  11. data/app/views/paper_trail_manager/changes/_version.html.erb +1 -1
  12. data/app/views/paper_trail_manager/changes/index.html.erb +41 -28
  13. data/app/views/paper_trail_manager/changes/show.html.erb +9 -6
  14. data/gemfiles/rails_6.1_paper_trail_12.0_kaminari.gemfile +10 -0
  15. data/gemfiles/rails_6.1_paper_trail_12.0_will_paginate.gemfile +10 -0
  16. data/gemfiles/rails_7.0_paper_trail_12.0_kaminari.gemfile +10 -0
  17. data/gemfiles/rails_7.0_paper_trail_12.0_will_paginate.gemfile +10 -0
  18. data/gemfiles/rails_7.0_paper_trail_15.0_kaminari.gemfile +10 -0
  19. data/gemfiles/rails_7.0_paper_trail_15.0_will_paginate.gemfile +10 -0
  20. data/gemfiles/rails_7.1_paper_trail_15.0_kaminari.gemfile +10 -0
  21. data/gemfiles/rails_7.1_paper_trail_15.0_will_paginate.gemfile +10 -0
  22. data/lib/paper_trail_manager.rb +4 -2
  23. data/paper_trail_manager.gemspec +14 -14
  24. data/spec/app_template.rb +19 -10
  25. data/spec/integration/authorization_spec.rb +84 -0
  26. data/spec/integration/date_filter_spec.rb +84 -0
  27. data/spec/integration/navigation_spec.rb +1 -1
  28. data/spec/integration/paper_trail_manager_spec.rb +6 -6
  29. data/spec/integration/response_formats_spec.rb +73 -0
  30. data/spec/support/factories.rb +2 -2
  31. data/spec/unit/authorization_spec.rb +42 -0
  32. data/spec/unit/changes_helper_spec.rb +81 -0
  33. metadata +75 -59
  34. data/gemfiles/rails_4.2.2_paper_trail_3.0_kaminari.gemfile +0 -13
  35. data/gemfiles/rails_4.2.2_paper_trail_3.0_will_paginate.gemfile +0 -13
  36. data/gemfiles/rails_4.2.2_paper_trail_4.0_kaminari.gemfile +0 -13
  37. data/gemfiles/rails_4.2.2_paper_trail_4.0_will_paginate.gemfile +0 -13
  38. data/gemfiles/rails_4.2.2_paper_trail_5.0_kaminari.gemfile +0 -13
  39. data/gemfiles/rails_4.2.2_paper_trail_5.0_will_paginate.gemfile +0 -13
  40. data/gemfiles/rails_4.2.2_paper_trail_6.0_kaminari.gemfile +0 -13
  41. data/gemfiles/rails_4.2.2_paper_trail_6.0_will_paginate.gemfile +0 -13
  42. data/gemfiles/rails_4.2.2_paper_trail_7.0_kaminari.gemfile +0 -13
  43. data/gemfiles/rails_4.2.2_paper_trail_7.0_will_paginate.gemfile +0 -13
  44. data/gemfiles/rails_4.2.2_paper_trail_8.0_kaminari.gemfile +0 -13
  45. data/gemfiles/rails_4.2.2_paper_trail_8.0_will_paginate.gemfile +0 -13
  46. data/gemfiles/rails_5.0.0_paper_trail_5.0_kaminari.gemfile +0 -13
  47. data/gemfiles/rails_5.0.0_paper_trail_5.0_will_paginate.gemfile +0 -13
  48. data/gemfiles/rails_5.0.0_paper_trail_6.0_kaminari.gemfile +0 -13
  49. data/gemfiles/rails_5.0.0_paper_trail_6.0_will_paginate.gemfile +0 -13
  50. data/gemfiles/rails_5.0.0_paper_trail_7.0_kaminari.gemfile +0 -13
  51. data/gemfiles/rails_5.0.0_paper_trail_7.0_will_paginate.gemfile +0 -13
  52. data/gemfiles/rails_5.0.0_paper_trail_8.0_kaminari.gemfile +0 -13
  53. data/gemfiles/rails_5.0.0_paper_trail_8.0_will_paginate.gemfile +0 -13
  54. data/gemfiles/rails_5.1.0_paper_trail_10.0_kaminari.gemfile +0 -13
  55. data/gemfiles/rails_5.1.0_paper_trail_10.0_will_paginate.gemfile +0 -13
  56. data/gemfiles/rails_5.1.0_paper_trail_7.0_kaminari.gemfile +0 -13
  57. data/gemfiles/rails_5.1.0_paper_trail_7.0_will_paginate.gemfile +0 -13
  58. data/gemfiles/rails_5.1.0_paper_trail_8.0_kaminari.gemfile +0 -13
  59. data/gemfiles/rails_5.1.0_paper_trail_8.0_will_paginate.gemfile +0 -13
  60. data/gemfiles/rails_5.1.0_paper_trail_9.0_kaminari.gemfile +0 -13
  61. data/gemfiles/rails_5.1.0_paper_trail_9.0_will_paginate.gemfile +0 -13
  62. data/gemfiles/rails_5.2.0_paper_trail_10.0_kaminari.gemfile +0 -13
  63. data/gemfiles/rails_5.2.0_paper_trail_10.0_will_paginate.gemfile +0 -13
  64. data/gemfiles/rails_5.2.0_paper_trail_9.0_kaminari.gemfile +0 -13
  65. data/gemfiles/rails_5.2.0_paper_trail_9.0_will_paginate.gemfile +0 -13
  66. data/gemfiles/rails_6.0.0_paper_trail_10.0_kaminari.gemfile +0 -14
  67. data/gemfiles/rails_6.0.0_paper_trail_10.0_will_paginate.gemfile +0 -15
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.0.0"
6
+ gem "sqlite3", "~> 1.7"
7
+ gem "paper_trail", "~> 15.0"
8
+ gem "kaminari", ">= 1.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.0.0"
6
+ gem "sqlite3", "~> 1.7"
7
+ gem "paper_trail", "~> 15.0"
8
+ gem "will_paginate", "~> 4.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.1.0"
6
+ gem "sqlite3", "~> 1.7"
7
+ gem "paper_trail", "~> 15.0"
8
+ gem "kaminari", ">= 1.0"
9
+
10
+ gemspec path: "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 7.1.0"
6
+ gem "sqlite3", "~> 1.7"
7
+ gem "paper_trail", "~> 15.0"
8
+ gem "will_paginate", "~> 4.0"
9
+
10
+ gemspec path: "../"
@@ -15,7 +15,9 @@ end
15
15
 
16
16
  class PaperTrailManager < Rails::Engine
17
17
  initializer 'paper_trail_manager.pagination' do
18
- ::ActionView::Base.send(:alias_method, :paginate, :will_paginate) if defined?(WillPaginate)
18
+ if defined?(WillPaginate)
19
+ ::ActionView::Base.define_method(:paginate) { |*args, **kwargs, &block| will_paginate(*args, **kwargs, &block) }
20
+ end
19
21
  end
20
22
 
21
23
  @@whodunnit_name_method = :name
@@ -50,7 +52,7 @@ class PaperTrailManager < Rails::Engine
50
52
  end
51
53
 
52
54
  def self.allow_show?(controller, version)
53
- allow_index_block.call controller, version
55
+ allow_show_block.call controller, version
54
56
  end
55
57
 
56
58
  # Describe when to allow reverts. Call this with a block that accepts
@@ -5,28 +5,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'paper_trail_manager'
8
- spec.version = '0.7.0'
8
+ spec.version = '0.8.0'
9
9
  spec.authors = ['Igal Koshevoy', 'Reid Beels']
10
- spec.authors = ['mail@reidbeels.com']
10
+ spec.email = ['mail@reidbeels.com']
11
11
  spec.summary = 'A user interface for `paper_trail` versioning data in Rails applications.'
12
12
  spec.description = 'Browse, subscribe, view and revert changes to records when using Rails and the `paper_trail` gem.'
13
- spec.homepage = 'https://github.com/fusion94/paper_trail_manager'
13
+ spec.homepage = 'https://github.com/DamageLabs/paper_trail_manager'
14
14
  spec.license = 'MIT'
15
-
16
15
  spec.files = `git ls-files -z`.split("\x0")
17
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
18
  spec.require_paths = ['lib']
20
-
21
- spec.add_dependency 'paper_trail', ['>= 3.0']
22
- spec.add_dependency 'rails', ['>= 3.0', '< 7.0']
23
-
24
- spec.add_development_dependency 'appraisal', '~> 1.0'
25
- spec.add_development_dependency 'factory_girl_rails', '~> 4.0'
26
- spec.add_development_dependency 'rake', '~> 10.4'
19
+ spec.required_ruby_version = '>= 3.1'
20
+ spec.add_dependency 'paper_trail', ['>= 12.0']
21
+ spec.add_dependency 'rails', ['>= 6.1', '< 8.0']
22
+ spec.add_dependency 'nokogiri', ['>= 1.18.3']
23
+ spec.add_dependency 'rails-html-sanitizer', ['>= 1.6.1']
24
+ spec.add_development_dependency 'appraisal', '~> 2.0'
25
+ spec.add_development_dependency 'factory_bot_rails', '~> 6.0'
26
+ spec.add_development_dependency 'rake', '~> 13.0'
27
27
  spec.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0'
28
- spec.add_development_dependency 'rspec-html-matchers', '~> 0.9.2'
28
+ spec.add_development_dependency 'rspec-html-matchers', '~> 0.10'
29
29
  spec.add_development_dependency 'rspec-its', '~> 1.0'
30
- spec.add_development_dependency 'rspec-rails', '~> 3.0'
31
- spec.add_development_dependency 'sqlite3', '>= 1.3.6'
30
+ spec.add_development_dependency 'rspec-rails', '~> 6.0'
31
+ spec.add_development_dependency 'sqlite3', '~> 1.7'
32
32
  end
data/spec/app_template.rb CHANGED
@@ -2,29 +2,38 @@
2
2
 
3
3
  gem 'paper_trail_manager', path: __FILE__ + '/../../../'
4
4
 
5
- unless File.exist?('app/assets/config/manifest.js')
6
- create_file 'app/assets/config/manifest.js'
7
- append_to_file 'app/assets/config/manifest.js', "//= link application.css\n"
8
- append_to_file 'app/assets/config/manifest.js', "//= link application.js\n"
9
- end
5
+ # Remove auto-generated request specs from dummy app
6
+ remove_file 'spec/requests/entities_spec.rb' if File.exist?('spec/requests/entities_spec.rb')
7
+ remove_file 'spec/requests/platforms_spec.rb' if File.exist?('spec/requests/platforms_spec.rb')
10
8
 
11
9
  generate 'paper_trail:install'
12
10
  generate 'resource', 'entity name:string status:string --no-controller-specs --no-helper-specs'
13
11
  generate 'resource', 'platform name:string status:string --no-controller-specs --no-helper-specs'
14
12
 
15
- remove_file 'spec/models/entity_spec.rb'
16
- remove_file 'spec/models/platform_spec.rb'
13
+ # Remove auto-generated spec files that conflict with our factories
14
+ remove_file 'spec/models/entity_spec.rb' if File.exist?('spec/models/entity_spec.rb')
15
+ remove_file 'spec/models/platform_spec.rb' if File.exist?('spec/models/platform_spec.rb')
17
16
 
18
17
  model_body = <<-MODEL
19
18
  has_paper_trail
20
19
 
21
- validates_presence_of :name
22
- validates_presence_of :status
20
+ validates :name, presence: true
21
+ validates :status, presence: true
23
22
  MODEL
24
23
 
25
24
  inject_into_class 'app/models/entity.rb', 'Entity', model_body
26
25
  inject_into_class 'app/models/platform.rb', 'Platform', model_body
27
26
 
28
- route "resources :changes, :controller => 'paper_trail_manager/changes'"
27
+ route "resources :changes, controller: 'paper_trail_manager/changes'"
28
+ route "root to: 'paper_trail_manager/changes#index'"
29
+
30
+ # Allow YAML deserialization of ActiveSupport::TimeWithZone (Ruby 3.1+ Psych 4)
31
+ initializer 'permitted_classes.rb', <<~RUBY
32
+ Rails.application.config.active_record.yaml_column_permitted_classes = [
33
+ ActiveSupport::TimeWithZone,
34
+ ActiveSupport::TimeZone,
35
+ Time
36
+ ]
37
+ RUBY
29
38
 
30
39
  rake 'db:migrate db:test:prepare'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PaperTrailManager, 'authorization integration', versioning: true do
6
+ let(:entity) { FactoryBot.create(:entity, name: 'Test Entity', status: 'Active') }
7
+
8
+ before do
9
+ entity
10
+ entity.update(status: 'Updated')
11
+ end
12
+
13
+ after do
14
+ default = proc { true }
15
+ PaperTrailManager.allow_index_block = default
16
+ PaperTrailManager.allow_show_block = default
17
+ PaperTrailManager.allow_revert_block = default
18
+ end
19
+
20
+ describe 'index' do
21
+ context 'when not authorized' do
22
+ before do
23
+ PaperTrailManager.allow_index_when { |_controller| false }
24
+ end
25
+
26
+ it 'redirects with an error flash' do
27
+ get changes_path
28
+ expect(response).to have_http_status(:redirect)
29
+ expect(flash[:error]).to eq('You do not have permission to list changes.')
30
+ end
31
+ end
32
+ end
33
+
34
+ describe 'show' do
35
+ context 'when not authorized' do
36
+ before do
37
+ PaperTrailManager.allow_show_when { |_controller, _version| false }
38
+ end
39
+
40
+ it 'redirects with an error flash' do
41
+ get change_path(entity.versions.last)
42
+ expect(response).to have_http_status(:redirect)
43
+ expect(flash[:error]).to eq('You do not have permission to show that change.')
44
+ end
45
+ end
46
+
47
+ context 'when change does not exist' do
48
+ it 'redirects with an error flash' do
49
+ get change_path(id: 999999)
50
+ expect(response).to have_http_status(:redirect)
51
+ expect(flash[:error]).to eq('No such version.')
52
+ end
53
+ end
54
+ end
55
+
56
+ describe 'revert' do
57
+ context 'when not authorized' do
58
+ before do
59
+ PaperTrailManager.allow_revert_when { |_controller, _version| false }
60
+ end
61
+
62
+ it 'redirects with an error flash' do
63
+ put change_path(entity.versions.last)
64
+ expect(response).to have_http_status(:redirect)
65
+ expect(flash[:error]).to eq('You do not have permission to revert this change.')
66
+ end
67
+ end
68
+
69
+ context 'when change does not exist' do
70
+ it 'redirects with an error flash' do
71
+ put change_path(id: 999999)
72
+ expect(response).to have_http_status(:redirect)
73
+ expect(flash[:error]).to eq('No such version.')
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'filtering by non-existent type' do
79
+ it 'returns an empty changes list' do
80
+ get changes_path(type: 'NonExistentModel')
81
+ expect(response.body).to include('No changes found')
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PaperTrailManager, 'date range filtering', versioning: true do
6
+ let(:entity) { FactoryBot.create(:entity, name: 'Test Entity', status: 'Active') }
7
+
8
+ before do
9
+ entity
10
+ entity.update(status: 'Updated')
11
+ end
12
+
13
+ describe 'index with date filters' do
14
+ context 'with from parameter' do
15
+ it 'shows changes on or after the date' do
16
+ get changes_path(from: Date.today.to_s)
17
+ expect(response).to have_http_status(:ok)
18
+ expect(response.body).to have_tag('.change_row')
19
+ end
20
+
21
+ it 'returns no changes for future date' do
22
+ get changes_path(from: (Date.today + 1).to_s)
23
+ expect(response.body).to include('No changes found')
24
+ end
25
+ end
26
+
27
+ context 'with to parameter' do
28
+ it 'shows changes on or before the date' do
29
+ get changes_path(to: Date.today.to_s)
30
+ expect(response).to have_http_status(:ok)
31
+ expect(response.body).to have_tag('.change_row')
32
+ end
33
+
34
+ it 'returns no changes for past date' do
35
+ get changes_path(to: (Date.today - 1).to_s)
36
+ expect(response.body).to include('No changes found')
37
+ end
38
+ end
39
+
40
+ context 'with from and to combined' do
41
+ it 'shows changes within the range' do
42
+ get changes_path(from: Date.today.to_s, to: Date.today.to_s)
43
+ expect(response).to have_http_status(:ok)
44
+ expect(response.body).to have_tag('.change_row')
45
+ end
46
+ end
47
+
48
+ context 'combined with type filter' do
49
+ it 'respects both date and type filters' do
50
+ get changes_path(from: Date.today.to_s, type: 'Entity')
51
+ expect(response).to have_http_status(:ok)
52
+ expect(response.body).to have_tag('.change_item', text: /Entity/)
53
+ end
54
+ end
55
+
56
+ context 'with invalid date' do
57
+ it 'ignores invalid from date gracefully' do
58
+ get changes_path(from: 'not-a-date')
59
+ expect(response).to have_http_status(:ok)
60
+ expect(response.body).to have_tag('.change_row')
61
+ end
62
+
63
+ it 'ignores invalid to date gracefully' do
64
+ get changes_path(to: 'garbage')
65
+ expect(response).to have_http_status(:ok)
66
+ expect(response.body).to have_tag('.change_row')
67
+ end
68
+ end
69
+
70
+ context 'date filter form' do
71
+ it 'renders date inputs' do
72
+ get changes_path
73
+ expect(response.body).to have_tag('input[type="date"][name="from"]')
74
+ expect(response.body).to have_tag('input[type="date"][name="to"]')
75
+ end
76
+
77
+ it 'preserves date values in form' do
78
+ get changes_path(from: '2026-01-01', to: '2026-12-31')
79
+ expect(response.body).to have_tag('input[name="from"][value="2026-01-01"]')
80
+ expect(response.body).to have_tag('input[name="to"][value="2026-12-31"]')
81
+ end
82
+ end
83
+ end
84
+ end
@@ -4,6 +4,6 @@ require 'spec_helper'
4
4
 
5
5
  describe 'Navigation' do
6
6
  it 'is a valid app' do
7
- ::Rails.application.should be_a_kind_of(Rails::Application)
7
+ expect(::Rails.application).to be_a_kind_of(Rails::Application)
8
8
  end
9
9
  end
@@ -14,11 +14,11 @@ describe PaperTrailManager, versioning: true do
14
14
  end
15
15
 
16
16
  context 'with changes' do
17
- let(:reimu) { FactoryGirl.create(:entity, name: 'Miko Hakurei Reimu', status: 'Highly Responsive to Prayers') }
18
- let(:flanchan) { FactoryGirl.create(:entity, name: 'Flandre Scarlet', status: 'The Embodiment of Scarlet Devil') }
19
- let(:sakuya) { FactoryGirl.create(:entity, name: 'Sakuya Izayoi', status: 'Flowering Night') }
20
- let(:kyuu_hachi) { FactoryGirl.create(:platform, name: 'PC-9801', status: 'SUGOI!!1!') }
21
- let!(:uinodouzu) { FactoryGirl.create(:platform, name: 'Mikorusofto Uinodouzu', status: 'o-O') }
17
+ let(:reimu) { FactoryBot.create(:entity, name: 'Miko Hakurei Reimu', status: 'Highly Responsive to Prayers') }
18
+ let(:flanchan) { FactoryBot.create(:entity, name: 'Flandre Scarlet', status: 'The Embodiment of Scarlet Devil') }
19
+ let(:sakuya) { FactoryBot.create(:entity, name: 'Sakuya Izayoi', status: 'Flowering Night') }
20
+ let(:kyuu_hachi) { FactoryBot.create(:platform, name: 'PC-9801', status: 'SUGOI!!1!') }
21
+ let!(:uinodouzu) { FactoryBot.create(:platform, name: 'Mikorusofto Uinodouzu', status: 'o-O') }
22
22
 
23
23
  let!(:flanchan_id) { flanchan.id }
24
24
 
@@ -161,7 +161,7 @@ describe PaperTrailManager, versioning: true do
161
161
  end
162
162
 
163
163
  it 'rollbacks a delete by restoring the record' do
164
- Entity.exists?(flanchan_id).should be_falsey
164
+ expect(Entity.exists?(flanchan_id)).to be_falsey
165
165
 
166
166
  put change_path(PaperTrail::Version.where(item_id: flanchan_id, item_type: 'Entity').last)
167
167
 
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PaperTrailManager, 'response formats', versioning: true do
6
+ let(:entity) { FactoryBot.create(:entity, name: 'Format Test', status: 'Active') }
7
+
8
+ before do
9
+ entity
10
+ entity.update(status: 'Updated')
11
+ end
12
+
13
+ describe 'JSON format' do
14
+ context 'index' do
15
+ it 'returns valid JSON with version data' do
16
+ get changes_path(format: :json)
17
+ expect(response.content_type).to include('application/json')
18
+ json = JSON.parse(response.body)
19
+ expect(json).to be_an(Array)
20
+ expect(json.length).to be >= 2
21
+ end
22
+
23
+ it 'respects type filter' do
24
+ get changes_path(format: :json, type: 'Entity')
25
+ json = JSON.parse(response.body)
26
+ json.each do |version|
27
+ expect(version['item_type']).to eq('Entity')
28
+ end
29
+ end
30
+
31
+ it 'respects id filter' do
32
+ get changes_path(format: :json, type: 'Entity', id: entity.id)
33
+ json = JSON.parse(response.body)
34
+ json.each do |version|
35
+ expect(version['item_id']).to eq(entity.id)
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'show' do
41
+ it 'returns valid JSON for a single version' do
42
+ version = entity.versions.last
43
+ get change_path(version, format: :json)
44
+ expect(response.content_type).to include('application/json')
45
+ json = JSON.parse(response.body)
46
+ expect(json['id']).to eq(version.id)
47
+ expect(json['item_type']).to eq('Entity')
48
+ end
49
+ end
50
+ end
51
+
52
+ describe 'Atom format' do
53
+ context 'index' do
54
+ it 'returns valid XML' do
55
+ get changes_path(format: :atom)
56
+ expect(response.content_type).to include('application/atom+xml')
57
+ expect(response.body).to include('<feed')
58
+ expect(response.body).to include('<entry>')
59
+ end
60
+
61
+ it 'includes version entries' do
62
+ get changes_path(format: :atom)
63
+ expect(response.body).to include('Entity')
64
+ end
65
+
66
+ it 'respects type filter' do
67
+ get changes_path(format: :atom, type: 'Entity')
68
+ expect(response.body).to include('Entity')
69
+ expect(response.body).not_to include('Platform')
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'factory_girl_rails'
3
+ require 'factory_bot_rails'
4
4
 
5
- FactoryGirl.define do
5
+ FactoryBot.define do
6
6
  factory :entity do
7
7
  sequence(:name) { |n| "name_#{n}" }
8
8
  sequence(:status) { |n| "status_#{n}" }
@@ -0,0 +1,42 @@
1
+ begin
2
+ require 'kaminari/core'
3
+ rescue LoadError
4
+ require 'will_paginate'
5
+ end
6
+ require 'paper_trail_manager'
7
+
8
+ RSpec.describe PaperTrailManager, 'authorization' do
9
+ let(:controller) { double('controller') }
10
+ let(:version) { double('version') }
11
+
12
+ after do
13
+ # Reset blocks to defaults
14
+ default = proc { true }
15
+ PaperTrailManager.allow_index_block = default
16
+ PaperTrailManager.allow_show_block = default
17
+ end
18
+
19
+ describe '.allow_show?' do
20
+ it 'uses allow_show_block, not allow_index_block' do
21
+ PaperTrailManager.allow_index_when { |_controller| false }
22
+ PaperTrailManager.allow_show_when { |_controller, _version| true }
23
+
24
+ expect(PaperTrailManager.allow_show?(controller, version)).to be true
25
+ end
26
+
27
+ it 'does not fall through to allow_index_block' do
28
+ PaperTrailManager.allow_index_when { |_controller| true }
29
+ PaperTrailManager.allow_show_when { |_controller, _version| false }
30
+
31
+ expect(PaperTrailManager.allow_show?(controller, version)).to be false
32
+ end
33
+ end
34
+
35
+ describe '.allow_index?' do
36
+ it 'uses allow_index_block' do
37
+ PaperTrailManager.allow_index_when { |_controller| true }
38
+
39
+ expect(PaperTrailManager.allow_index?(controller)).to be true
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PaperTrailManager::ChangesHelper, versioning: true do
6
+ # Use a view context to get access to content_tag, h, etc.
7
+ let(:helper) { ApplicationController.new.view_context }
8
+
9
+ describe '#text_or_nil' do
10
+ it 'returns styled nil for nil values' do
11
+ result = helper.text_or_nil(nil)
12
+ expect(result).to include('em')
13
+ expect(result).to include('nil')
14
+ end
15
+
16
+ it 'returns escaped text for non-nil values' do
17
+ result = helper.text_or_nil('hello')
18
+ expect(result).to eq('hello')
19
+ end
20
+
21
+ it 'escapes HTML in values' do
22
+ result = helper.text_or_nil('<script>alert("xss")</script>')
23
+ expect(result).not_to include('<script>')
24
+ end
25
+ end
26
+
27
+ describe '#changes_for' do
28
+ let(:entity) { FactoryBot.create(:entity, name: 'Test', status: 'Active') }
29
+
30
+ context 'for a create event' do
31
+ it 'returns a hash' do
32
+ version = entity.versions.first
33
+ changes = helper.changes_for(version)
34
+ expect(changes).to be_a(Hash)
35
+ end
36
+ end
37
+
38
+ context 'for an update event' do
39
+ before { entity.update(status: 'Updated') }
40
+
41
+ it 'returns a hash' do
42
+ version = entity.versions.last
43
+ changes = helper.changes_for(version)
44
+ expect(changes).to be_a(Hash)
45
+ end
46
+ end
47
+
48
+ context 'for a destroy event' do
49
+ before { entity.destroy }
50
+
51
+ it 'returns changes with previous values' do
52
+ version = PaperTrail::Version.where(item_type: 'Entity', event: 'destroy').last
53
+ changes = helper.changes_for(version)
54
+ expect(changes).to be_a(Hash)
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#change_item_types' do
60
+ it 'returns a sorted array of versioned model names' do
61
+ # Ensure models are loaded
62
+ Entity.new
63
+ types = helper.change_item_types
64
+ expect(types).to be_an(Array)
65
+ expect(types).to include('Entity')
66
+ expect(types).to eq(types.sort)
67
+ end
68
+ end
69
+
70
+ describe '#version_reify' do
71
+ let(:entity) { FactoryBot.create(:entity, name: 'Test', status: 'Active') }
72
+
73
+ it 'returns the reified record for an update' do
74
+ entity.update(status: 'Updated')
75
+ version = entity.versions.last
76
+ record = helper.version_reify(version)
77
+ expect(record).to be_a(Entity)
78
+ expect(record.status).to eq('Active')
79
+ end
80
+ end
81
+ end