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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +13 -47
- data/.gitignore +2 -2
- data/.ruby-version +1 -0
- data/Appraisals +9 -17
- data/CHANGES.md +18 -0
- data/README.md +130 -66
- data/Rakefile +5 -3
- data/app/controllers/paper_trail_manager/changes_controller.rb +16 -1
- data/app/helpers/paper_trail_manager/changes_helper.rb +2 -2
- data/app/views/paper_trail_manager/changes/_version.html.erb +1 -1
- data/app/views/paper_trail_manager/changes/index.html.erb +41 -28
- data/app/views/paper_trail_manager/changes/show.html.erb +9 -6
- data/gemfiles/rails_6.1_paper_trail_12.0_kaminari.gemfile +10 -0
- data/gemfiles/rails_6.1_paper_trail_12.0_will_paginate.gemfile +10 -0
- data/gemfiles/rails_7.0_paper_trail_12.0_kaminari.gemfile +10 -0
- data/gemfiles/rails_7.0_paper_trail_12.0_will_paginate.gemfile +10 -0
- data/gemfiles/rails_7.0_paper_trail_15.0_kaminari.gemfile +10 -0
- data/gemfiles/rails_7.0_paper_trail_15.0_will_paginate.gemfile +10 -0
- data/gemfiles/rails_7.1_paper_trail_15.0_kaminari.gemfile +10 -0
- data/gemfiles/rails_7.1_paper_trail_15.0_will_paginate.gemfile +10 -0
- data/lib/paper_trail_manager.rb +4 -2
- data/paper_trail_manager.gemspec +14 -14
- data/spec/app_template.rb +19 -10
- data/spec/integration/authorization_spec.rb +84 -0
- data/spec/integration/date_filter_spec.rb +84 -0
- data/spec/integration/navigation_spec.rb +1 -1
- data/spec/integration/paper_trail_manager_spec.rb +6 -6
- data/spec/integration/response_formats_spec.rb +73 -0
- data/spec/support/factories.rb +2 -2
- data/spec/unit/authorization_spec.rb +42 -0
- data/spec/unit/changes_helper_spec.rb +81 -0
- metadata +75 -59
- data/gemfiles/rails_4.2.2_paper_trail_3.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_3.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_4.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_4.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_5.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_5.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_6.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_6.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_7.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_7.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_8.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_4.2.2_paper_trail_8.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_5.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_5.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_6.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_6.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_7.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_7.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_8.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.0.0_paper_trail_8.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_10.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_10.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_7.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_7.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_8.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_8.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_9.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.1.0_paper_trail_9.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.2.0_paper_trail_10.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.2.0_paper_trail_10.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_5.2.0_paper_trail_9.0_kaminari.gemfile +0 -13
- data/gemfiles/rails_5.2.0_paper_trail_9.0_will_paginate.gemfile +0 -13
- data/gemfiles/rails_6.0.0_paper_trail_10.0_kaminari.gemfile +0 -14
- data/gemfiles/rails_6.0.0_paper_trail_10.0_will_paginate.gemfile +0 -15
data/lib/paper_trail_manager.rb
CHANGED
|
@@ -15,7 +15,9 @@ end
|
|
|
15
15
|
|
|
16
16
|
class PaperTrailManager < Rails::Engine
|
|
17
17
|
initializer 'paper_trail_manager.pagination' do
|
|
18
|
-
|
|
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
|
-
|
|
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
|
data/paper_trail_manager.gemspec
CHANGED
|
@@ -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.
|
|
8
|
+
spec.version = '0.8.0'
|
|
9
9
|
spec.authors = ['Igal Koshevoy', 'Reid Beels']
|
|
10
|
-
spec.
|
|
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/
|
|
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', ['>=
|
|
22
|
-
spec.add_dependency 'rails', ['>=
|
|
23
|
-
|
|
24
|
-
spec.
|
|
25
|
-
spec.add_development_dependency '
|
|
26
|
-
spec.add_development_dependency '
|
|
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.
|
|
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', '~>
|
|
31
|
-
spec.add_development_dependency 'sqlite3', '
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
16
|
-
remove_file 'spec/models/
|
|
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
|
-
|
|
22
|
-
|
|
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, :
|
|
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
|
|
@@ -14,11 +14,11 @@ describe PaperTrailManager, versioning: true do
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
context 'with changes' do
|
|
17
|
-
let(:reimu) {
|
|
18
|
-
let(:flanchan) {
|
|
19
|
-
let(:sakuya) {
|
|
20
|
-
let(:kyuu_hachi) {
|
|
21
|
-
let!(:uinodouzu) {
|
|
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).
|
|
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
|
data/spec/support/factories.rb
CHANGED
|
@@ -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
|