Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions spec/app_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
inject_into_class 'app/models/platform.rb', 'Platform', model_body

route "resources :changes, controller: 'paper_trail_manager/changes'"
route "root to: 'paper_trail_manager/changes#index'"

# Allow YAML deserialization of ActiveSupport::TimeWithZone (Ruby 3.1+ Psych 4)
initializer 'permitted_classes.rb', <<~RUBY
Expand Down
84 changes: 84 additions & 0 deletions spec/integration/authorization_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

require 'spec_helper'

describe PaperTrailManager, 'authorization integration', versioning: true do
let(:entity) { FactoryBot.create(:entity, name: 'Test Entity', status: 'Active') }

before do
entity
entity.update(status: 'Updated')
end

after do
default = proc { true }
PaperTrailManager.allow_index_block = default
PaperTrailManager.allow_show_block = default
PaperTrailManager.allow_revert_block = default
end

describe 'index' do
context 'when not authorized' do
before do
PaperTrailManager.allow_index_when { |_controller| false }
end

it 'redirects with an error flash' do
get changes_path
expect(response).to have_http_status(:redirect)
expect(flash[:error]).to eq('You do not have permission to list changes.')
end
end
end

describe 'show' do
context 'when not authorized' do
before do
PaperTrailManager.allow_show_when { |_controller, _version| false }
end

it 'redirects with an error flash' do
get change_path(entity.versions.last)
expect(response).to have_http_status(:redirect)
expect(flash[:error]).to eq('You do not have permission to show that change.')
end
end

context 'when change does not exist' do
it 'redirects with an error flash' do
get change_path(id: 999999)
expect(response).to have_http_status(:redirect)
expect(flash[:error]).to eq('No such version.')
end
end
end

describe 'revert' do
context 'when not authorized' do
before do
PaperTrailManager.allow_revert_when { |_controller, _version| false }
end

it 'redirects with an error flash' do
put change_path(entity.versions.last)
expect(response).to have_http_status(:redirect)
expect(flash[:error]).to eq('You do not have permission to revert this change.')
end
end

context 'when change does not exist' do
it 'redirects with an error flash' do
put change_path(id: 999999)
expect(response).to have_http_status(:redirect)
expect(flash[:error]).to eq('No such version.')
end
end
end

describe 'filtering by non-existent type' do
it 'returns an empty changes list' do
get changes_path(type: 'NonExistentModel')
expect(response.body).to include('No changes found')
end
end
end
73 changes: 73 additions & 0 deletions spec/integration/response_formats_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require 'spec_helper'

describe PaperTrailManager, 'response formats', versioning: true do
let(:entity) { FactoryBot.create(:entity, name: 'Format Test', status: 'Active') }

before do
entity
entity.update(status: 'Updated')
end

describe 'JSON format' do
context 'index' do
it 'returns valid JSON with version data' do
get changes_path(format: :json)
expect(response.content_type).to include('application/json')
json = JSON.parse(response.body)
expect(json).to be_an(Array)
expect(json.length).to be >= 2
end

it 'respects type filter' do
get changes_path(format: :json, type: 'Entity')
json = JSON.parse(response.body)
json.each do |version|
expect(version['item_type']).to eq('Entity')
end
end

it 'respects id filter' do
get changes_path(format: :json, type: 'Entity', id: entity.id)
json = JSON.parse(response.body)
json.each do |version|
expect(version['item_id']).to eq(entity.id)
end
end
end

context 'show' do
it 'returns valid JSON for a single version' do
version = entity.versions.last
get change_path(version, format: :json)
expect(response.content_type).to include('application/json')
json = JSON.parse(response.body)
expect(json['id']).to eq(version.id)
expect(json['item_type']).to eq('Entity')
end
end
end

describe 'Atom format' do
context 'index' do
it 'returns valid XML' do
get changes_path(format: :atom)
expect(response.content_type).to include('application/atom+xml')
expect(response.body).to include('<feed')
expect(response.body).to include('<entry>')
end

it 'includes version entries' do
get changes_path(format: :atom)
expect(response.body).to include('Entity')
end

it 'respects type filter' do
get changes_path(format: :atom, type: 'Entity')
expect(response.body).to include('Entity')
expect(response.body).not_to include('Platform')
end
end
end
end
81 changes: 81 additions & 0 deletions spec/unit/changes_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require 'spec_helper'

describe PaperTrailManager::ChangesHelper, versioning: true do
# Use a view context to get access to content_tag, h, etc.
let(:helper) { ApplicationController.new.view_context }

describe '#text_or_nil' do
it 'returns styled nil for nil values' do
result = helper.text_or_nil(nil)
expect(result).to include('em')
expect(result).to include('nil')
end

it 'returns escaped text for non-nil values' do
result = helper.text_or_nil('hello')
expect(result).to eq('hello')
end

it 'escapes HTML in values' do
result = helper.text_or_nil('<script>alert("xss")</script>')
expect(result).not_to include('<script>')
end
end

describe '#changes_for' do
let(:entity) { FactoryBot.create(:entity, name: 'Test', status: 'Active') }

context 'for a create event' do
it 'returns a hash' do
version = entity.versions.first
changes = helper.changes_for(version)
expect(changes).to be_a(Hash)
end
end

context 'for an update event' do
before { entity.update(status: 'Updated') }

it 'returns a hash' do
version = entity.versions.last
changes = helper.changes_for(version)
expect(changes).to be_a(Hash)
end
end

context 'for a destroy event' do
before { entity.destroy }

it 'returns changes with previous values' do
version = PaperTrail::Version.where(item_type: 'Entity', event: 'destroy').last
changes = helper.changes_for(version)
expect(changes).to be_a(Hash)
end
end
end

describe '#change_item_types' do
it 'returns a sorted array of versioned model names' do
# Ensure models are loaded
Entity.new
types = helper.change_item_types
expect(types).to be_an(Array)
expect(types).to include('Entity')
expect(types).to eq(types.sort)
end
end

describe '#version_reify' do
let(:entity) { FactoryBot.create(:entity, name: 'Test', status: 'Active') }

it 'returns the reified record for an update' do
entity.update(status: 'Updated')
version = entity.versions.last
record = helper.version_reify(version)
expect(record).to be_a(Entity)
expect(record.status).to eq('Active')
end
end
end
Loading