audited 4.2.2 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of audited might be problematic. Click here for more details.

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -9
  3. data/Appraisals +10 -6
  4. data/Gemfile +1 -13
  5. data/README.md +46 -33
  6. data/Rakefile +3 -18
  7. data/gemfiles/rails40.gemfile +1 -5
  8. data/gemfiles/rails41.gemfile +1 -5
  9. data/gemfiles/rails42.gemfile +1 -5
  10. data/gemfiles/rails50.gemfile +8 -0
  11. data/lib/audited-rspec.rb +4 -0
  12. data/lib/audited.rb +15 -2
  13. data/lib/audited/audit.rb +97 -57
  14. data/lib/audited/auditor.rb +73 -45
  15. data/lib/audited/rspec_matchers.rb +6 -2
  16. data/lib/audited/sweeper.rb +12 -23
  17. data/lib/audited/version.rb +1 -1
  18. data/lib/generators/audited/install_generator.rb +20 -0
  19. data/lib/generators/audited/migration.rb +15 -0
  20. data/lib/generators/audited/templates/add_association_to_audits.rb +11 -0
  21. data/lib/generators/audited/templates/add_comment_to_audits.rb +9 -0
  22. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +10 -0
  23. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +10 -0
  24. data/lib/generators/audited/templates/install.rb +30 -0
  25. data/lib/generators/audited/templates/rename_association_to_associated.rb +23 -0
  26. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +9 -0
  27. data/lib/generators/audited/templates/rename_parent_to_association.rb +11 -0
  28. data/lib/generators/audited/upgrade_generator.rb +57 -0
  29. data/spec/audited/audit_spec.rb +199 -0
  30. data/spec/audited/auditor_spec.rb +607 -0
  31. data/spec/audited/sweeper_spec.rb +106 -0
  32. data/spec/audited_spec_helpers.rb +6 -22
  33. data/spec/rails_app/config/environments/test.rb +7 -4
  34. data/spec/rails_app/config/initializers/secret_token.rb +1 -1
  35. data/spec/rails_app/config/routes.rb +1 -4
  36. data/spec/spec_helper.rb +7 -9
  37. data/spec/support/active_record/models.rb +20 -13
  38. data/spec/support/active_record/schema.rb +36 -12
  39. data/test/db/version_1.rb +4 -4
  40. data/test/db/version_2.rb +4 -4
  41. data/test/db/version_3.rb +4 -4
  42. data/test/db/version_4.rb +4 -4
  43. data/test/db/version_5.rb +2 -2
  44. data/test/db/version_6.rb +2 -2
  45. data/test/install_generator_test.rb +1 -1
  46. data/test/upgrade_generator_test.rb +10 -10
  47. metadata +73 -37
  48. data/lib/audited/active_record/version.rb +0 -5
  49. data/lib/audited/mongo_mapper/version.rb +0 -5
  50. data/spec/support/mongo_mapper/connection.rb +0 -4
  51. data/spec/support/mongo_mapper/models.rb +0 -214
@@ -0,0 +1,23 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ if index_exists? :audits, [:association_id, :association_type], :name => 'association_index'
4
+ remove_index :audits, :name => 'association_index'
5
+ end
6
+
7
+ rename_column :audits, :association_id, :associated_id
8
+ rename_column :audits, :association_type, :associated_type
9
+
10
+ add_index :audits, [:associated_id, :associated_type], :name => 'associated_index'
11
+ end
12
+
13
+ def self.down
14
+ if index_exists? :audits, [:associated_id, :associated_type], :name => 'associated_index'
15
+ remove_index :audits, :name => 'associated_index'
16
+ end
17
+
18
+ rename_column :audits, :associated_type, :association_type
19
+ rename_column :audits, :associated_id, :association_id
20
+
21
+ add_index :audits, [:association_id, :association_type], :name => 'association_index'
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :audits, :changes, :audited_changes
4
+ end
5
+
6
+ def self.down
7
+ rename_column :audits, :audited_changes, :changes
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ rename_column :audits, :auditable_parent_id, :association_id
4
+ rename_column :audits, :auditable_parent_type, :association_type
5
+ end
6
+
7
+ def self.down
8
+ rename_column :audits, :association_type, :auditable_parent_type
9
+ rename_column :audits, :association_id, :auditable_parent_id
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+ require 'active_record'
4
+ require 'rails/generators/active_record'
5
+ require 'generators/audited/migration'
6
+
7
+ module Audited
8
+ module Generators
9
+ class UpgradeGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+ extend Audited::Generators::Migration
12
+
13
+ source_root File.expand_path("../templates", __FILE__)
14
+
15
+ def copy_templates
16
+ migrations_to_be_applied do |m|
17
+ migration_template "#{m}.rb", "db/migrate/#{m}.rb"
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def migrations_to_be_applied
24
+ Audited::Audit.reset_column_information
25
+ columns = Audited::Audit.columns.map(&:name)
26
+
27
+ yield :add_comment_to_audits unless columns.include?('comment')
28
+
29
+ if columns.include?('changes')
30
+ yield :rename_changes_to_audited_changes
31
+ end
32
+
33
+ unless columns.include?('remote_address')
34
+ yield :add_remote_address_to_audits
35
+ end
36
+
37
+ unless columns.include?('request_uuid')
38
+ yield :add_request_uuid_to_audits
39
+ end
40
+
41
+ unless columns.include?('association_id')
42
+ if columns.include?('auditable_parent_id')
43
+ yield :rename_parent_to_association
44
+ else
45
+ unless columns.include?('associated_id')
46
+ yield :add_association_to_audits
47
+ end
48
+ end
49
+ end
50
+
51
+ if columns.include?('association_id')
52
+ yield :rename_association_to_associated
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,199 @@
1
+ require "spec_helper"
2
+
3
+ describe Audited::Audit do
4
+ let(:user) { Models::ActiveRecord::User.new name: "Testing" }
5
+
6
+ describe "user=" do
7
+
8
+ it "should be able to set the user to a model object" do
9
+ subject.user = user
10
+ expect(subject.user).to eq(user)
11
+ end
12
+
13
+ it "should be able to set the user to nil" do
14
+ subject.user_id = 1
15
+ subject.user_type = 'Models::ActiveRecord::User'
16
+ subject.username = 'joe'
17
+
18
+ subject.user = nil
19
+
20
+ expect(subject.user).to be_nil
21
+ expect(subject.user_id).to be_nil
22
+ expect(subject.user_type).to be_nil
23
+ expect(subject.username).to be_nil
24
+ end
25
+
26
+ it "should be able to set the user to a string" do
27
+ subject.user = 'test'
28
+ expect(subject.user).to eq('test')
29
+ end
30
+
31
+ it "should clear model when setting to a string" do
32
+ subject.user = user
33
+ subject.user = 'testing'
34
+ expect(subject.user_id).to be_nil
35
+ expect(subject.user_type).to be_nil
36
+ end
37
+
38
+ it "should clear the username when setting to a model" do
39
+ subject.username = 'test'
40
+ subject.user = user
41
+ expect(subject.username).to be_nil
42
+ end
43
+
44
+ end
45
+
46
+ describe "revision" do
47
+
48
+ it "should recreate attributes" do
49
+ user = Models::ActiveRecord::User.create name: "1"
50
+ 5.times {|i| user.update_attribute :name, (i + 2).to_s }
51
+
52
+ user.audits.each do |audit|
53
+ expect(audit.revision.name).to eq(audit.version.to_s)
54
+ end
55
+ end
56
+
57
+ it "should set protected attributes" do
58
+ u = Models::ActiveRecord::User.create(name: "Brandon")
59
+ u.update_attribute :logins, 1
60
+ u.update_attribute :logins, 2
61
+
62
+ expect(u.audits[2].revision.logins).to eq(2)
63
+ expect(u.audits[1].revision.logins).to eq(1)
64
+ expect(u.audits[0].revision.logins).to eq(0)
65
+ end
66
+
67
+ it "should bypass attribute assignment wrappers" do
68
+ u = Models::ActiveRecord::User.create(name: "<Joe>")
69
+ expect(u.audits.first.revision.name).to eq("&lt;Joe&gt;")
70
+ end
71
+
72
+ it "should work for deleted records" do
73
+ user = Models::ActiveRecord::User.create name: "1"
74
+ user.destroy
75
+ revision = user.audits.last.revision
76
+ expect(revision.name).to eq(user.name)
77
+ expect(revision).to be_a_new_record
78
+ end
79
+ end
80
+
81
+ it "should set the version number on create" do
82
+ user = Models::ActiveRecord::User.create! name: "Set Version Number"
83
+ expect(user.audits.first.version).to eq(1)
84
+ user.update_attribute :name, "Set to 2"
85
+ expect(user.audits.reload.first.version).to eq(1)
86
+ expect(user.audits.reload.last.version).to eq(2)
87
+ user.destroy
88
+ expect(Audited::Audit.where(auditable_type: "Models::ActiveRecord::User", auditable_id: user.id).last.version).to eq(3)
89
+ end
90
+
91
+ it "should set the request uuid on create" do
92
+ user = Models::ActiveRecord::User.create! name: "Set Request UUID"
93
+ expect(user.audits.reload.first.request_uuid).not_to be_blank
94
+ end
95
+
96
+ describe "reconstruct_attributes" do
97
+ it "should work with the old way of storing just the new value" do
98
+ audits = Audited::Audit.reconstruct_attributes([Audited::Audit.new(audited_changes: {"attribute" => "value"})])
99
+ expect(audits["attribute"]).to eq("value")
100
+ end
101
+ end
102
+
103
+ describe "audited_classes" do
104
+ class Models::ActiveRecord::CustomUser < ::ActiveRecord::Base
105
+ end
106
+ class Models::ActiveRecord::CustomUserSubclass < Models::ActiveRecord::CustomUser
107
+ audited
108
+ end
109
+
110
+ it "should include audited classes" do
111
+ expect(Audited::Audit.audited_classes).to include(Models::ActiveRecord::User)
112
+ end
113
+
114
+ it "should include subclasses" do
115
+ expect(Audited::Audit.audited_classes).to include(Models::ActiveRecord::CustomUserSubclass)
116
+ end
117
+ end
118
+
119
+ describe "new_attributes" do
120
+ it "should return a hash of the new values" do
121
+ new_attributes = Audited::Audit.new(audited_changes: {a: [1, 2], b: [3, 4]}).new_attributes
122
+ expect(new_attributes).to eq({"a" => 2, "b" => 4})
123
+ end
124
+ end
125
+
126
+ describe "old_attributes" do
127
+ it "should return a hash of the old values" do
128
+ old_attributes = Audited::Audit.new(audited_changes: {a: [1, 2], b: [3, 4]}).old_attributes
129
+ expect(old_attributes).to eq({"a" => 1, "b" => 3})
130
+ end
131
+ end
132
+
133
+ describe "as_user" do
134
+ it "should record user objects" do
135
+ Audited::Audit.as_user(user) do
136
+ company = Models::ActiveRecord::Company.create name: "The auditors"
137
+ company.name = "The Auditors, Inc"
138
+ company.save
139
+
140
+ company.audits.each do |audit|
141
+ expect(audit.user).to eq(user)
142
+ end
143
+ end
144
+ end
145
+
146
+ it "should record usernames" do
147
+ Audited::Audit.as_user(user.name) do
148
+ company = Models::ActiveRecord::Company.create name: "The auditors"
149
+ company.name = "The Auditors, Inc"
150
+ company.save
151
+
152
+ company.audits.each do |audit|
153
+ expect(audit.username).to eq(user.name)
154
+ end
155
+ end
156
+ end
157
+
158
+ it "should be thread safe" do
159
+ begin
160
+ expect(user.save).to eq(true)
161
+
162
+ t1 = Thread.new do
163
+ Audited::Audit.as_user(user) do
164
+ sleep 1
165
+ expect(Models::ActiveRecord::Company.create(name: "The Auditors, Inc").audits.first.user).to eq(user)
166
+ end
167
+ end
168
+
169
+ t2 = Thread.new do
170
+ Audited::Audit.as_user(user.name) do
171
+ expect(Models::ActiveRecord::Company.create(name: "The Competing Auditors, LLC").audits.first.username).to eq(user.name)
172
+ sleep 0.5
173
+ end
174
+ end
175
+
176
+ t1.join
177
+ t2.join
178
+ end
179
+ end if ActiveRecord::Base.connection.adapter_name != 'SQLite'
180
+
181
+ it "should return the value from the yield block" do
182
+ result = Audited::Audit.as_user('foo') do
183
+ 42
184
+ end
185
+ expect(result).to eq(42)
186
+ end
187
+
188
+ it "should reset audited_user when the yield block raises an exception" do
189
+ expect {
190
+ Audited::Audit.as_user('foo') do
191
+ raise StandardError.new('expected')
192
+ end
193
+ }.to raise_exception('expected')
194
+ expect(Thread.current[:audited_user]).to be_nil
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,607 @@
1
+ require "spec_helper"
2
+
3
+ describe Audited::Auditor do
4
+
5
+ describe "configuration" do
6
+ it "should include instance methods" do
7
+ expect(Models::ActiveRecord::User.new).to be_a_kind_of( Audited::Auditor::AuditedInstanceMethods)
8
+ end
9
+
10
+ it "should include class methods" do
11
+ expect(Models::ActiveRecord::User).to be_a_kind_of( Audited::Auditor::AuditedClassMethods )
12
+ end
13
+
14
+ ['created_at', 'updated_at', 'created_on', 'updated_on', 'lock_version', 'id', 'password'].each do |column|
15
+ it "should not audit #{column}" do
16
+ expect(Models::ActiveRecord::User.non_audited_columns).to include(column)
17
+ end
18
+ end
19
+
20
+ it "should be configurable which attributes are not audited" do
21
+ Audited.ignored_attributes = ['delta', 'top_secret', 'created_at']
22
+ class Secret < ::ActiveRecord::Base
23
+ audited
24
+ end
25
+
26
+ expect(Secret.non_audited_columns).to include('delta', 'top_secret', 'created_at')
27
+ end
28
+
29
+ it "should not save non-audited columns" do
30
+ expect(create_user.audits.first.audited_changes.keys.any? { |col| ['created_at', 'updated_at', 'password'].include?( col ) }).to eq(false)
31
+ end
32
+
33
+ it "should not save other columns than specified in 'only' option" do
34
+ user = Models::ActiveRecord::UserOnlyPassword.create
35
+ user.instance_eval do
36
+ def non_column_attr
37
+ @non_column_attr
38
+ end
39
+
40
+ def non_column_attr=(val)
41
+ attribute_will_change!("non_column_attr")
42
+ @non_column_attr = val
43
+ end
44
+ end
45
+
46
+ user.password = "password"
47
+ user.non_column_attr = "some value"
48
+ user.save!
49
+ expect(user.audits.last.audited_changes.keys).to eq(%w{password})
50
+ end
51
+ end
52
+
53
+ describe :new do
54
+ it "should allow mass assignment of all unprotected attributes" do
55
+ yesterday = 1.day.ago
56
+
57
+ u = Models::ActiveRecord::NoAttributeProtectionUser.new(name: 'name',
58
+ username: 'username',
59
+ password: 'password',
60
+ activated: true,
61
+ suspended_at: yesterday,
62
+ logins: 2)
63
+
64
+ expect(u.name).to eq('name')
65
+ expect(u.username).to eq('username')
66
+ expect(u.password).to eq('password')
67
+ expect(u.activated).to eq(true)
68
+ expect(u.suspended_at.to_i).to eq(yesterday.to_i)
69
+ expect(u.logins).to eq(2)
70
+ end
71
+ end
72
+
73
+ describe "on create" do
74
+ let( :user ) { create_user audit_comment: "Create" }
75
+
76
+ it "should change the audit count" do
77
+ expect {
78
+ user
79
+ }.to change( Audited::Audit, :count ).by(1)
80
+ end
81
+
82
+ it "should create associated audit" do
83
+ expect(user.audits.count).to eq(1)
84
+ end
85
+
86
+ it "should set the action to create" do
87
+ expect(user.audits.first.action).to eq('create')
88
+ expect(Audited::Audit.creates.order(:id).last).to eq(user.audits.first)
89
+ expect(user.audits.creates.count).to eq(1)
90
+ expect(user.audits.updates.count).to eq(0)
91
+ expect(user.audits.destroys.count).to eq(0)
92
+ end
93
+
94
+ it "should store all the audited attributes" do
95
+ expect(user.audits.first.audited_changes).to eq(user.audited_attributes)
96
+ end
97
+
98
+ it "should store comment" do
99
+ expect(user.audits.first.comment).to eq('Create')
100
+ end
101
+
102
+ it "should not audit an attribute which is excepted if specified on create or destroy" do
103
+ on_create_destroy_except_name = Models::ActiveRecord::OnCreateDestroyExceptName.create(name: 'Bart')
104
+ expect(on_create_destroy_except_name.audits.first.audited_changes.keys.any?{|col| ['name'].include? col}).to eq(false)
105
+ end
106
+
107
+ it "should not save an audit if only specified on update/destroy" do
108
+ expect {
109
+ Models::ActiveRecord::OnUpdateDestroy.create!( name: 'Bart' )
110
+ }.to_not change( Audited::Audit, :count )
111
+ end
112
+ end
113
+
114
+ describe "on update" do
115
+ before do
116
+ @user = create_user( name: 'Brandon', audit_comment: 'Update' )
117
+ end
118
+
119
+ it "should save an audit" do
120
+ expect {
121
+ @user.update_attribute(:name, "Someone")
122
+ }.to change( Audited::Audit, :count ).by(1)
123
+ expect {
124
+ @user.update_attribute(:name, "Someone else")
125
+ }.to change( Audited::Audit, :count ).by(1)
126
+ end
127
+
128
+ it "should set the action to 'update'" do
129
+ @user.update_attributes name: 'Changed'
130
+ expect(@user.audits.last.action).to eq('update')
131
+ expect(Audited::Audit.updates.order(:id).last).to eq(@user.audits.last)
132
+ expect(@user.audits.updates.last).to eq(@user.audits.last)
133
+ end
134
+
135
+ it "should store the changed attributes" do
136
+ @user.update_attributes name: 'Changed'
137
+ expect(@user.audits.last.audited_changes).to eq({ 'name' => ['Brandon', 'Changed'] })
138
+ end
139
+
140
+ it "should store audit comment" do
141
+ expect(@user.audits.last.comment).to eq('Update')
142
+ end
143
+
144
+ it "should not save an audit if only specified on create/destroy" do
145
+ on_create_destroy = Models::ActiveRecord::OnCreateDestroy.create( name: 'Bart' )
146
+ expect {
147
+ on_create_destroy.update_attributes name: 'Changed'
148
+ }.to_not change( Audited::Audit, :count )
149
+ end
150
+
151
+ it "should not save an audit if the value doesn't change after type casting" do
152
+ @user.update_attributes! logins: 0, activated: true
153
+ expect { @user.update_attribute :logins, '0' }.to_not change( Audited::Audit, :count )
154
+ expect { @user.update_attribute :activated, 1 }.to_not change( Audited::Audit, :count )
155
+ expect { @user.update_attribute :activated, '1' }.to_not change( Audited::Audit, :count )
156
+ end
157
+
158
+ describe "with no dirty changes" do
159
+ it "does not create an audit if the record is not changed" do
160
+ expect {
161
+ @user.save!
162
+ }.to_not change( Audited::Audit, :count )
163
+ end
164
+
165
+ it "creates an audit when an audit comment is present" do
166
+ expect {
167
+ @user.audit_comment = "Comment"
168
+ @user.save!
169
+ }.to change( Audited::Audit, :count )
170
+ end
171
+ end
172
+ end
173
+
174
+ describe "on destroy" do
175
+ before do
176
+ @user = create_user
177
+ end
178
+
179
+ it "should save an audit" do
180
+ expect {
181
+ @user.destroy
182
+ }.to change( Audited::Audit, :count )
183
+
184
+ expect(@user.audits.size).to eq(2)
185
+ end
186
+
187
+ it "should set the action to 'destroy'" do
188
+ @user.destroy
189
+
190
+ expect(@user.audits.last.action).to eq('destroy')
191
+ expect(Audited::Audit.destroys.order(:id).last).to eq(@user.audits.last)
192
+ expect(@user.audits.destroys.last).to eq(@user.audits.last)
193
+ end
194
+
195
+ it "should store all of the audited attributes" do
196
+ @user.destroy
197
+
198
+ expect(@user.audits.last.audited_changes).to eq(@user.audited_attributes)
199
+ end
200
+
201
+ it "should be able to reconstruct a destroyed record without history" do
202
+ @user.audits.delete_all
203
+ @user.destroy
204
+
205
+ revision = @user.audits.first.revision
206
+ expect(revision.name).to eq(@user.name)
207
+ end
208
+
209
+ it "should not save an audit if only specified on create/update" do
210
+ on_create_update = Models::ActiveRecord::OnCreateUpdate.create!( name: 'Bart' )
211
+
212
+ expect {
213
+ on_create_update.destroy
214
+ }.to_not change( Audited::Audit, :count )
215
+ end
216
+
217
+ it "should audit dependent destructions" do
218
+ owner = Models::ActiveRecord::Owner.create!
219
+ company = owner.companies.create!
220
+
221
+ expect {
222
+ owner.destroy
223
+ }.to change( Audited::Audit, :count )
224
+
225
+ expect(company.audits.map { |a| a.action }).to eq(['create', 'destroy'])
226
+ end
227
+ end
228
+
229
+ describe "on destroy with unsaved object" do
230
+ let(:user) { Models::ActiveRecord::User.new }
231
+
232
+ it "should not audit on 'destroy'" do
233
+ expect {
234
+ user.destroy
235
+ }.to_not raise_error
236
+
237
+ expect( user.audits ).to be_empty
238
+ end
239
+ end
240
+
241
+ describe "associated with" do
242
+ let(:owner) { Models::ActiveRecord::Owner.create(name: 'Models::ActiveRecord::Owner') }
243
+ let(:owned_company) { Models::ActiveRecord::OwnedCompany.create!(name: 'The auditors', owner: owner) }
244
+
245
+ it "should record the associated object on create" do
246
+ expect(owned_company.audits.first.associated).to eq(owner)
247
+ end
248
+
249
+ it "should store the associated object on update" do
250
+ owned_company.update_attribute(:name, 'The Auditors')
251
+ expect(owned_company.audits.last.associated).to eq(owner)
252
+ end
253
+
254
+ it "should store the associated object on destroy" do
255
+ owned_company.destroy
256
+ expect(owned_company.audits.last.associated).to eq(owner)
257
+ end
258
+ end
259
+
260
+ describe "has associated audits" do
261
+ let!(:owner) { Models::ActiveRecord::Owner.create!(name: 'Models::ActiveRecord::Owner') }
262
+ let!(:owned_company) { Models::ActiveRecord::OwnedCompany.create!(name: 'The auditors', owner: owner) }
263
+
264
+ it "should list the associated audits" do
265
+ expect(owner.associated_audits.length).to eq(1)
266
+ expect(owner.associated_audits.first.auditable).to eq(owned_company)
267
+ end
268
+ end
269
+
270
+ describe "revisions" do
271
+ let( :user ) { create_versions }
272
+
273
+ it "should return an Array of Users" do
274
+ expect(user.revisions).to be_a_kind_of( Array )
275
+ user.revisions.each { |version| expect(version).to be_a_kind_of Models::ActiveRecord::User }
276
+ end
277
+
278
+ it "should have one revision for a new record" do
279
+ expect(create_user.revisions.size).to eq(1)
280
+ end
281
+
282
+ it "should have one revision for each audit" do
283
+ expect(user.audits.size).to eql( user.revisions.size )
284
+ end
285
+
286
+ it "should set the attributes for each revision" do
287
+ u = Models::ActiveRecord::User.create(name: 'Brandon', username: 'brandon')
288
+ u.update_attributes name: 'Foobar'
289
+ u.update_attributes name: 'Awesome', username: 'keepers'
290
+
291
+ expect(u.revisions.size).to eql(3)
292
+
293
+ expect(u.revisions[0].name).to eql('Brandon')
294
+ expect(u.revisions[0].username).to eql('brandon')
295
+
296
+ expect(u.revisions[1].name).to eql('Foobar')
297
+ expect(u.revisions[1].username).to eql('brandon')
298
+
299
+ expect(u.revisions[2].name).to eql('Awesome')
300
+ expect(u.revisions[2].username).to eql('keepers')
301
+ end
302
+
303
+ it "access to only recent revisions" do
304
+ u = Models::ActiveRecord::User.create(name: 'Brandon', username: 'brandon')
305
+ u.update_attributes name: 'Foobar'
306
+ u.update_attributes name: 'Awesome', username: 'keepers'
307
+
308
+ expect(u.revisions(2).size).to eq(2)
309
+
310
+ expect(u.revisions(2)[0].name).to eq('Foobar')
311
+ expect(u.revisions(2)[0].username).to eq('brandon')
312
+
313
+ expect(u.revisions(2)[1].name).to eq('Awesome')
314
+ expect(u.revisions(2)[1].username).to eq('keepers')
315
+ end
316
+
317
+ it "should be empty if no audits exist" do
318
+ user.audits.delete_all
319
+ expect(user.revisions).to be_empty
320
+ end
321
+
322
+ it "should ignore attributes that have been deleted" do
323
+ user.audits.last.update_attributes audited_changes: {old_attribute: 'old value'}
324
+ expect { user.revisions }.to_not raise_error
325
+ end
326
+ end
327
+
328
+ describe "revisions" do
329
+ let( :user ) { create_versions(5) }
330
+
331
+ it "should maintain identity" do
332
+ expect(user.revision(1)).to eq(user)
333
+ end
334
+
335
+ it "should find the given revision" do
336
+ revision = user.revision(3)
337
+ expect(revision).to be_a_kind_of( Models::ActiveRecord::User )
338
+ expect(revision.version).to eq(3)
339
+ expect(revision.name).to eq('Foobar 3')
340
+ end
341
+
342
+ it "should find the previous revision with :previous" do
343
+ revision = user.revision(:previous)
344
+ expect(revision.version).to eq(4)
345
+ #expect(revision).to eq(user.revision(4))
346
+ expect(revision.attributes).to eq(user.revision(4).attributes)
347
+ end
348
+
349
+ it "should be able to get the previous revision repeatedly" do
350
+ previous = user.revision(:previous)
351
+ expect(previous.version).to eq(4)
352
+ expect(previous.revision(:previous).version).to eq(3)
353
+ end
354
+
355
+ it "should be able to set protected attributes" do
356
+ u = Models::ActiveRecord::User.create(name: 'Brandon')
357
+ u.update_attribute :logins, 1
358
+ u.update_attribute :logins, 2
359
+
360
+ expect(u.revision(3).logins).to eq(2)
361
+ expect(u.revision(2).logins).to eq(1)
362
+ expect(u.revision(1).logins).to eq(0)
363
+ end
364
+
365
+ it "should set attributes directly" do
366
+ u = Models::ActiveRecord::User.create(name: '<Joe>')
367
+ expect(u.revision(1).name).to eq('&lt;Joe&gt;')
368
+ end
369
+
370
+ it "should set the attributes for each revision" do
371
+ u = Models::ActiveRecord::User.create(name: 'Brandon', username: 'brandon')
372
+ u.update_attributes name: 'Foobar'
373
+ u.update_attributes name: 'Awesome', username: 'keepers'
374
+
375
+ expect(u.revision(3).name).to eq('Awesome')
376
+ expect(u.revision(3).username).to eq('keepers')
377
+
378
+ expect(u.revision(2).name).to eq('Foobar')
379
+ expect(u.revision(2).username).to eq('brandon')
380
+
381
+ expect(u.revision(1).name).to eq('Brandon')
382
+ expect(u.revision(1).username).to eq('brandon')
383
+ end
384
+
385
+ it "should be able to get time for first revision" do
386
+ suspended_at = Time.zone.now
387
+ u = Models::ActiveRecord::User.create(suspended_at: suspended_at)
388
+ expect(u.revision(1).suspended_at.to_s).to eq(suspended_at.to_s)
389
+ end
390
+
391
+ it "should not raise an error when no previous audits exist" do
392
+ user.audits.destroy_all
393
+ expect { user.revision(:previous) }.to_not raise_error
394
+ end
395
+
396
+ it "should mark revision's attributes as changed" do
397
+ expect(user.revision(1).name_changed?).to eq(true)
398
+ end
399
+
400
+ it "should record new audit when saving revision" do
401
+ expect {
402
+ user.revision(1).save!
403
+ }.to change( user.audits, :count ).by(1)
404
+ end
405
+
406
+ it "should re-insert destroyed records" do
407
+ user.destroy
408
+ expect {
409
+ user.revision(1).save!
410
+ }.to change( Models::ActiveRecord::User, :count ).by(1)
411
+ end
412
+ end
413
+
414
+ describe "revision_at" do
415
+ let( :user ) { create_user }
416
+
417
+ it "should find the latest revision before the given time" do
418
+ audit = user.audits.first
419
+ audit.created_at = 1.hour.ago
420
+ audit.save!
421
+ user.update_attributes name: 'updated'
422
+ expect(user.revision_at( 2.minutes.ago ).version).to eq(1)
423
+ end
424
+
425
+ it "should be nil if given a time before audits" do
426
+ expect(user.revision_at( 1.week.ago )).to be_nil
427
+ end
428
+ end
429
+
430
+ describe "without auditing" do
431
+ it "should not save an audit when calling #save_without_auditing" do
432
+ expect {
433
+ u = Models::ActiveRecord::User.new(name: 'Brandon')
434
+ expect(u.save_without_auditing).to eq(true)
435
+ }.to_not change( Audited::Audit, :count )
436
+ end
437
+
438
+ it "should not save an audit inside of the #without_auditing block" do
439
+ expect {
440
+ Models::ActiveRecord::User.without_auditing { Models::ActiveRecord::User.create!( name: 'Brandon' ) }
441
+ }.to_not change( Audited::Audit, :count )
442
+ end
443
+
444
+ it "should reset auditing status even it raises an exception" do
445
+ Models::ActiveRecord::User.without_auditing { raise } rescue nil
446
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
447
+ end
448
+
449
+ it "should be thread safe using a #without_auditing block" do
450
+ begin
451
+ t1 = Thread.new do
452
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
453
+ Models::ActiveRecord::User.without_auditing do
454
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
455
+ Models::ActiveRecord::User.create!( name: 'Bart' )
456
+ sleep 1
457
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
458
+ end
459
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
460
+ end
461
+
462
+ t2 = Thread.new do
463
+ sleep 0.5
464
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
465
+ Models::ActiveRecord::User.create!( name: 'Lisa' )
466
+ end
467
+ t1.join
468
+ t2.join
469
+
470
+ expect(Models::ActiveRecord::User.find_by_name('Bart').audits.count).to eq(0)
471
+ expect(Models::ActiveRecord::User.find_by_name('Lisa').audits.count).to eq(1)
472
+ rescue ActiveRecord::StatementInvalid
473
+ STDERR.puts "Thread safety tests cannot be run with SQLite"
474
+ end
475
+ end
476
+ end
477
+
478
+ describe "comment required" do
479
+
480
+ describe "on create" do
481
+ it "should not validate when audit_comment is not supplied" do
482
+ expect(Models::ActiveRecord::CommentRequiredUser.new).not_to be_valid
483
+ end
484
+
485
+ it "should validate when audit_comment is supplied" do
486
+ expect(Models::ActiveRecord::CommentRequiredUser.new( audit_comment: 'Create')).to be_valid
487
+ end
488
+
489
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
490
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
491
+ expect(Models::ActiveRecord::CommentRequiredUser.new).to be_valid
492
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
493
+ end
494
+ end
495
+
496
+ describe "on update" do
497
+ let( :user ) { Models::ActiveRecord::CommentRequiredUser.create!( audit_comment: 'Create' ) }
498
+
499
+ it "should not validate when audit_comment is not supplied" do
500
+ expect(user.update_attributes(name: 'Test')).to eq(false)
501
+ end
502
+
503
+ it "should validate when audit_comment is supplied" do
504
+ expect(user.update_attributes(name: 'Test', audit_comment: 'Update')).to eq(true)
505
+ end
506
+
507
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
508
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
509
+ expect(user.update_attributes(name: 'Test')).to eq(true)
510
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
511
+ end
512
+ end
513
+
514
+ describe "on destroy" do
515
+ let( :user ) { Models::ActiveRecord::CommentRequiredUser.create!( audit_comment: 'Create' )}
516
+
517
+ it "should not validate when audit_comment is not supplied" do
518
+ expect(user.destroy).to eq(false)
519
+ end
520
+
521
+ it "should validate when audit_comment is supplied" do
522
+ user.audit_comment = "Destroy"
523
+ expect(user.destroy).to eq(user)
524
+ end
525
+
526
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
527
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
528
+ expect(user.destroy).to eq(user)
529
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
530
+ end
531
+ end
532
+
533
+ end
534
+
535
+ describe "attr_protected and attr_accessible" do
536
+
537
+ it "should not raise error when attr_accessible is set and protected is false" do
538
+ expect {
539
+ Models::ActiveRecord::AccessibleAfterDeclarationUser.new(name: 'No fail!')
540
+ }.to_not raise_error
541
+ end
542
+
543
+ it "should not rause an error when attr_accessible is declared before audited" do
544
+ expect {
545
+ Models::ActiveRecord::AccessibleAfterDeclarationUser.new(name: 'No fail!')
546
+ }.to_not raise_error
547
+ end
548
+ end
549
+
550
+ describe "audit_as" do
551
+ let( :user ) { Models::ActiveRecord::User.create name: 'Testing' }
552
+
553
+ it "should record user objects" do
554
+ Models::ActiveRecord::Company.audit_as( user ) do
555
+ company = Models::ActiveRecord::Company.create name: 'The auditors'
556
+ company.update_attributes name: 'The Auditors'
557
+
558
+ company.audits.each do |audit|
559
+ expect(audit.user).to eq(user)
560
+ end
561
+ end
562
+ end
563
+
564
+ it "should record usernames" do
565
+ Models::ActiveRecord::Company.audit_as( user.name ) do
566
+ company = Models::ActiveRecord::Company.create name: 'The auditors'
567
+ company.update_attributes name: 'The Auditors'
568
+
569
+ company.audits.each do |audit|
570
+ expect(audit.user).to eq(user.name)
571
+ end
572
+ end
573
+ end
574
+ end
575
+
576
+ describe "after_audit" do
577
+ let( :user ) { user = Models::ActiveRecord::UserWithAfterAudit.new }
578
+
579
+ it "should invoke after_audit callback on create" do
580
+ expect(user.bogus_attr).to be_nil
581
+ expect(user.save).to eq(true)
582
+ expect(user.bogus_attr).to eq("do something")
583
+ end
584
+ end
585
+
586
+ describe "around_audit" do
587
+ let( :user ) { user = Models::ActiveRecord::UserWithAfterAudit.new }
588
+
589
+ it "should invoke around_audit callback on create" do
590
+ expect(user.around_attr).to be_nil
591
+ expect(user.save).to eq(true)
592
+ expect(user.around_attr).to eq(user.audits.last)
593
+ end
594
+ end
595
+
596
+ describe "STI auditing" do
597
+ it "should correctly disable auditing when using STI" do
598
+ company = Models::ActiveRecord::Company::STICompany.create name: 'The auditors'
599
+ expect(company.type).to eq("Models::ActiveRecord::Company::STICompany")
600
+ expect {
601
+ Models::ActiveRecord::Company.auditing_enabled = false
602
+ company.update_attributes name: 'STI auditors'
603
+ Models::ActiveRecord::Company.auditing_enabled = true
604
+ }.to_not change( Audited.audit_class, :count )
605
+ end
606
+ end
607
+ end