diff --git a/spec/app_template.rb b/spec/app_template.rb index 5fc93eb..f3756c2 100644 --- a/spec/app_template.rb +++ b/spec/app_template.rb @@ -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 diff --git a/spec/integration/authorization_spec.rb b/spec/integration/authorization_spec.rb new file mode 100644 index 0000000..2e9e55c --- /dev/null +++ b/spec/integration/authorization_spec.rb @@ -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 diff --git a/spec/integration/response_formats_spec.rb b/spec/integration/response_formats_spec.rb new file mode 100644 index 0000000..3241e83 --- /dev/null +++ b/spec/integration/response_formats_spec.rb @@ -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('') + 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 diff --git a/spec/unit/changes_helper_spec.rb b/spec/unit/changes_helper_spec.rb new file mode 100644 index 0000000..778d087 --- /dev/null +++ b/spec/unit/changes_helper_spec.rb @@ -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('') + expect(result).not_to include('