velocity_audited 5.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +115 -0
  3. data/.gitignore +17 -0
  4. data/.standard.yml +5 -0
  5. data/.yardopts +3 -0
  6. data/Appraisals +44 -0
  7. data/CHANGELOG.md +419 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE +19 -0
  10. data/README.md +433 -0
  11. data/Rakefile +18 -0
  12. data/gemfiles/rails50.gemfile +10 -0
  13. data/gemfiles/rails51.gemfile +10 -0
  14. data/gemfiles/rails52.gemfile +10 -0
  15. data/gemfiles/rails60.gemfile +10 -0
  16. data/gemfiles/rails61.gemfile +10 -0
  17. data/gemfiles/rails70.gemfile +10 -0
  18. data/lib/audited/audit.rb +198 -0
  19. data/lib/audited/auditor.rb +476 -0
  20. data/lib/audited/railtie.rb +16 -0
  21. data/lib/audited/rspec_matchers.rb +228 -0
  22. data/lib/audited/sweeper.rb +67 -0
  23. data/lib/audited/version.rb +5 -0
  24. data/lib/audited-rspec.rb +6 -0
  25. data/lib/audited.rb +49 -0
  26. data/lib/generators/audited/install_generator.rb +27 -0
  27. data/lib/generators/audited/migration.rb +17 -0
  28. data/lib/generators/audited/migration_helper.rb +11 -0
  29. data/lib/generators/audited/templates/add_association_to_audits.rb +13 -0
  30. data/lib/generators/audited/templates/add_comment_to_audits.rb +11 -0
  31. data/lib/generators/audited/templates/add_remote_address_to_audits.rb +12 -0
  32. data/lib/generators/audited/templates/add_request_uuid_to_audits.rb +12 -0
  33. data/lib/generators/audited/templates/add_version_to_auditable_index.rb +23 -0
  34. data/lib/generators/audited/templates/install.rb +32 -0
  35. data/lib/generators/audited/templates/rename_association_to_associated.rb +25 -0
  36. data/lib/generators/audited/templates/rename_changes_to_audited_changes.rb +11 -0
  37. data/lib/generators/audited/templates/rename_parent_to_association.rb +13 -0
  38. data/lib/generators/audited/templates/revert_polymorphic_indexes_order.rb +22 -0
  39. data/lib/generators/audited/upgrade_generator.rb +70 -0
  40. data/lib/velocity_audited.rb +5 -0
  41. data/spec/audited/audit_spec.rb +357 -0
  42. data/spec/audited/auditor_spec.rb +1097 -0
  43. data/spec/audited/rspec_matchers_spec.rb +69 -0
  44. data/spec/audited/sweeper_spec.rb +133 -0
  45. data/spec/audited_spec.rb +18 -0
  46. data/spec/audited_spec_helpers.rb +32 -0
  47. data/spec/rails_app/app/assets/config/manifest.js +2 -0
  48. data/spec/rails_app/config/application.rb +13 -0
  49. data/spec/rails_app/config/database.yml +26 -0
  50. data/spec/rails_app/config/environment.rb +5 -0
  51. data/spec/rails_app/config/environments/test.rb +47 -0
  52. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  53. data/spec/rails_app/config/initializers/inflections.rb +2 -0
  54. data/spec/rails_app/config/initializers/secret_token.rb +3 -0
  55. data/spec/rails_app/config/routes.rb +3 -0
  56. data/spec/spec_helper.rb +24 -0
  57. data/spec/support/active_record/models.rb +151 -0
  58. data/spec/support/active_record/postgres/1_change_audited_changes_type_to_json.rb +11 -0
  59. data/spec/support/active_record/postgres/2_change_audited_changes_type_to_jsonb.rb +11 -0
  60. data/spec/support/active_record/schema.rb +90 -0
  61. data/test/db/version_1.rb +17 -0
  62. data/test/db/version_2.rb +18 -0
  63. data/test/db/version_3.rb +18 -0
  64. data/test/db/version_4.rb +19 -0
  65. data/test/db/version_5.rb +17 -0
  66. data/test/db/version_6.rb +19 -0
  67. data/test/install_generator_test.rb +62 -0
  68. data/test/test_helper.rb +18 -0
  69. data/test/upgrade_generator_test.rb +97 -0
  70. metadata +260 -0
@@ -0,0 +1,1097 @@
1
+ require "spec_helper"
2
+
3
+ SingleCov.covered! uncovered: 9 # not testing proxy_respond_to? hack / 2 methods / deprecation of `version`
4
+
5
+ class ConditionalPrivateCompany < ::ActiveRecord::Base
6
+ self.table_name = "companies"
7
+
8
+ audited if: :foo?
9
+
10
+ private def foo?
11
+ true
12
+ end
13
+ end
14
+
15
+ class ConditionalCompany < ::ActiveRecord::Base
16
+ self.table_name = "companies"
17
+
18
+ audited if: :public?
19
+
20
+ def public?
21
+ end
22
+ end
23
+
24
+ class ExclusiveCompany < ::ActiveRecord::Base
25
+ self.table_name = "companies"
26
+ audited if: proc { false }
27
+ end
28
+
29
+ class ExclusionaryCompany < ::ActiveRecord::Base
30
+ self.table_name = "companies"
31
+
32
+ audited unless: :non_profit?
33
+
34
+ def non_profit?
35
+ end
36
+ end
37
+
38
+ class ExclusionaryCompany2 < ::ActiveRecord::Base
39
+ self.table_name = "companies"
40
+ audited unless: proc { |c| c.exclusive? }
41
+
42
+ def exclusive?
43
+ true
44
+ end
45
+ end
46
+
47
+ class InclusiveCompany < ::ActiveRecord::Base
48
+ self.table_name = "companies"
49
+ audited if: proc { true }
50
+ end
51
+
52
+ class InclusiveCompany2 < ::ActiveRecord::Base
53
+ self.table_name = "companies"
54
+ audited unless: proc { false }
55
+ end
56
+
57
+ class Secret < ::ActiveRecord::Base
58
+ audited
59
+ end
60
+
61
+ class Secret2 < ::ActiveRecord::Base
62
+ audited
63
+ self.non_audited_columns = ["delta", "top_secret", "created_at"]
64
+ end
65
+
66
+ describe Audited::Auditor do
67
+ describe "configuration" do
68
+ it "should include instance methods" do
69
+ expect(Models::ActiveRecord::User.new).to be_a_kind_of(Audited::Auditor::AuditedInstanceMethods)
70
+ end
71
+
72
+ it "should include class methods" do
73
+ expect(Models::ActiveRecord::User).to be_a_kind_of(Audited::Auditor::AuditedClassMethods)
74
+ end
75
+
76
+ ["created_at", "updated_at", "created_on", "updated_on", "lock_version", "id", "password"].each do |column|
77
+ it "should not audit #{column}" do
78
+ expect(Models::ActiveRecord::User.non_audited_columns).to include(column)
79
+ end
80
+ end
81
+
82
+ context "should be configurable which conditions are audited" do
83
+ subject { ConditionalCompany.new.send(:auditing_enabled) }
84
+
85
+ context "when condition method is private" do
86
+ subject { ConditionalPrivateCompany.new.send(:auditing_enabled) }
87
+
88
+ it { is_expected.to be_truthy }
89
+ end
90
+
91
+ context "when passing a method name" do
92
+ context "when conditions are true" do
93
+ before { allow_any_instance_of(ConditionalCompany).to receive(:public?).and_return(true) }
94
+ it { is_expected.to be_truthy }
95
+ end
96
+
97
+ context "when conditions are false" do
98
+ before { allow_any_instance_of(ConditionalCompany).to receive(:public?).and_return(false) }
99
+ it { is_expected.to be_falsey }
100
+ end
101
+ end
102
+
103
+ context "when passing a Proc" do
104
+ context "when conditions are true" do
105
+ subject { InclusiveCompany.new.send(:auditing_enabled) }
106
+
107
+ it { is_expected.to be_truthy }
108
+ end
109
+
110
+ context "when conditions are false" do
111
+ subject { ExclusiveCompany.new.send(:auditing_enabled) }
112
+ it { is_expected.to be_falsey }
113
+ end
114
+ end
115
+ end
116
+
117
+ context "should be configurable which conditions aren't audited" do
118
+ context "when using a method name" do
119
+ subject { ExclusionaryCompany.new.send(:auditing_enabled) }
120
+
121
+ context "when conditions are true" do
122
+ before { allow_any_instance_of(ExclusionaryCompany).to receive(:non_profit?).and_return(true) }
123
+ it { is_expected.to be_falsey }
124
+ end
125
+
126
+ context "when conditions are false" do
127
+ before { allow_any_instance_of(ExclusionaryCompany).to receive(:non_profit?).and_return(false) }
128
+ it { is_expected.to be_truthy }
129
+ end
130
+ end
131
+
132
+ context "when using a proc" do
133
+ context "when conditions are true" do
134
+ subject { ExclusionaryCompany2.new.send(:auditing_enabled) }
135
+ it { is_expected.to be_falsey }
136
+ end
137
+
138
+ context "when conditions are false" do
139
+ subject { InclusiveCompany2.new.send(:auditing_enabled) }
140
+ it { is_expected.to be_truthy }
141
+ end
142
+ end
143
+ end
144
+
145
+ it "should be configurable which attributes are not audited via ignored_attributes" do
146
+ Audited.ignored_attributes = ["delta", "top_secret", "created_at"]
147
+
148
+ expect(Secret.non_audited_columns).to include("delta", "top_secret", "created_at")
149
+ end
150
+
151
+ it "should be configurable which attributes are not audited via non_audited_columns=" do
152
+ expect(Secret2.non_audited_columns).to include("delta", "top_secret", "created_at")
153
+ end
154
+
155
+ it "should not save non-audited columns" do
156
+ previous = Models::ActiveRecord::User.non_audited_columns
157
+ begin
158
+ Models::ActiveRecord::User.non_audited_columns += [:favourite_device]
159
+
160
+ expect(create_user.audits.first.audited_changes.keys.any? { |col| ["favourite_device", "created_at", "updated_at", "password"].include?(col) }).to eq(false)
161
+ ensure
162
+ Models::ActiveRecord::User.non_audited_columns = previous
163
+ end
164
+ end
165
+
166
+ it "should not save other columns than specified in 'only' option" do
167
+ user = Models::ActiveRecord::UserOnlyPassword.create
168
+ user.instance_eval do
169
+ def non_column_attr
170
+ @non_column_attr
171
+ end
172
+
173
+ def non_column_attr=(val)
174
+ attribute_will_change!("non_column_attr")
175
+ @non_column_attr = val
176
+ end
177
+ end
178
+
179
+ user.password = "password"
180
+ user.non_column_attr = "some value"
181
+ user.save!
182
+ expect(user.audits.last.audited_changes.keys).to eq(%w[password])
183
+ end
184
+
185
+ it "should save attributes not specified in 'except' option" do
186
+ user = Models::ActiveRecord::User.create
187
+ user.instance_eval do
188
+ def non_column_attr
189
+ @non_column_attr
190
+ end
191
+
192
+ def non_column_attr=(val)
193
+ attribute_will_change!("non_column_attr")
194
+ @non_column_attr = val
195
+ end
196
+ end
197
+
198
+ user.password = "password"
199
+ user.non_column_attr = "some value"
200
+ user.save!
201
+ expect(user.audits.last.audited_changes.keys).to eq(%w[non_column_attr])
202
+ end
203
+
204
+ it "should redact columns specified in 'redacted' option" do
205
+ redacted = Audited::Auditor::AuditedInstanceMethods::REDACTED
206
+ user = Models::ActiveRecord::UserRedactedPassword.create(password: "password")
207
+ user.save!
208
+ expect(user.audits.last.audited_changes["password"]).to eq(redacted)
209
+ user.password = "new_password"
210
+ user.save!
211
+ expect(user.audits.last.audited_changes["password"]).to eq([redacted, redacted])
212
+ end
213
+
214
+ it "should redact columns specified in 'redacted' option when there are multiple specified" do
215
+ redacted = Audited::Auditor::AuditedInstanceMethods::REDACTED
216
+ user =
217
+ Models::ActiveRecord::UserMultipleRedactedAttributes.create(
218
+ password: "password",
219
+ ssn: 123456789
220
+ )
221
+ user.save!
222
+ expect(user.audits.last.audited_changes["password"]).to eq(redacted)
223
+ expect(user.audits.last.audited_changes["ssn"]).to eq(redacted)
224
+ user.password = "new_password"
225
+ user.ssn = 987654321
226
+ user.save!
227
+ expect(user.audits.last.audited_changes["password"]).to eq([redacted, redacted])
228
+ expect(user.audits.last.audited_changes["ssn"]).to eq([redacted, redacted])
229
+ end
230
+
231
+ it "should redact columns in 'redacted' column with custom option" do
232
+ user = Models::ActiveRecord::UserRedactedPasswordCustomRedaction.create(password: "password")
233
+ user.save!
234
+ expect(user.audits.last.audited_changes["password"]).to eq(["My", "Custom", "Value", 7])
235
+ end
236
+
237
+ if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
238
+ describe "'json' and 'jsonb' audited_changes column type" do
239
+ let(:migrations_path) { SPEC_ROOT.join("support/active_record/postgres") }
240
+
241
+ after do
242
+ run_migrations(:down, migrations_path)
243
+ end
244
+
245
+ it "should work if column type is 'json'" do
246
+ run_migrations(:up, migrations_path, 1)
247
+ Audited::Audit.reset_column_information
248
+ expect(Audited::Audit.columns_hash["audited_changes"].sql_type).to eq("json")
249
+
250
+ user = Models::ActiveRecord::User.create
251
+ user.name = "new name"
252
+ user.save!
253
+ expect(user.audits.last.audited_changes).to eq({"name" => [nil, "new name"]})
254
+ end
255
+
256
+ it "should work if column type is 'jsonb'" do
257
+ run_migrations(:up, migrations_path, 2)
258
+ Audited::Audit.reset_column_information
259
+ expect(Audited::Audit.columns_hash["audited_changes"].sql_type).to eq("jsonb")
260
+
261
+ user = Models::ActiveRecord::User.create
262
+ user.name = "new name"
263
+ user.save!
264
+ expect(user.audits.last.audited_changes).to eq({"name" => [nil, "new name"]})
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ describe :new do
271
+ it "should allow mass assignment of all unprotected attributes" do
272
+ yesterday = 1.day.ago
273
+
274
+ u = Models::ActiveRecord::NoAttributeProtectionUser.new(name: "name",
275
+ username: "username",
276
+ password: "password",
277
+ activated: true,
278
+ suspended_at: yesterday,
279
+ logins: 2)
280
+
281
+ expect(u.name).to eq("name")
282
+ expect(u.username).to eq("username")
283
+ expect(u.password).to eq("password")
284
+ expect(u.activated).to eq(true)
285
+ expect(u.suspended_at.to_i).to eq(yesterday.to_i)
286
+ expect(u.logins).to eq(2)
287
+ end
288
+ end
289
+
290
+ describe "on create" do
291
+ let(:user) { create_user status: :reliable, audit_comment: "Create" }
292
+
293
+ it "should change the audit count" do
294
+ expect {
295
+ user
296
+ }.to change(Audited::Audit, :count).by(1)
297
+ end
298
+
299
+ it "should create associated audit" do
300
+ expect(user.audits.count).to eq(1)
301
+ end
302
+
303
+ it "should set the action to create" do
304
+ expect(user.audits.first.action).to eq("create")
305
+ expect(Audited::Audit.creates.order(:id).last).to eq(user.audits.first)
306
+ expect(user.audits.creates.count).to eq(1)
307
+ expect(user.audits.updates.count).to eq(0)
308
+ expect(user.audits.destroys.count).to eq(0)
309
+ end
310
+
311
+ it "should store all the audited attributes" do
312
+ expect(user.audits.first.audited_changes).to eq(user.audited_attributes)
313
+ end
314
+
315
+ it "should store enum value" do
316
+ expect(user.audits.first.audited_changes["status"]).to eq(1)
317
+ end
318
+
319
+ context "when store_synthesized_enums is set to true" do
320
+ before { Audited.store_synthesized_enums = true }
321
+ after { Audited.store_synthesized_enums = false }
322
+
323
+ it "should store enum value as Rails synthesized value" do
324
+ expect(user.audits.first.audited_changes["status"]).to eq("reliable")
325
+ end
326
+ end
327
+
328
+ it "should store comment" do
329
+ expect(user.audits.first.comment).to eq("Create")
330
+ end
331
+
332
+ it "should not audit an attribute which is excepted if specified on create or destroy" do
333
+ on_create_destroy_except_name = Models::ActiveRecord::OnCreateDestroyExceptName.create(name: "Bart")
334
+ expect(on_create_destroy_except_name.audits.first.audited_changes.keys.any? { |col| ["name"].include? col }).to eq(false)
335
+ end
336
+
337
+ it "should not save an audit if only specified on update/destroy" do
338
+ expect {
339
+ Models::ActiveRecord::OnUpdateDestroy.create!(name: "Bart")
340
+ }.to_not change(Audited::Audit, :count)
341
+ end
342
+ end
343
+
344
+ describe "on update" do
345
+ before do
346
+ @user = create_user(name: "Brandon", status: :active, audit_comment: "Update")
347
+ end
348
+
349
+ it "should save an audit" do
350
+ expect {
351
+ @user.update_attribute(:name, "Someone")
352
+ }.to change(Audited::Audit, :count).by(1)
353
+ expect {
354
+ @user.update_attribute(:name, "Someone else")
355
+ }.to change(Audited::Audit, :count).by(1)
356
+ end
357
+
358
+ it "should set the action to 'update'" do
359
+ @user.update! name: "Changed"
360
+ expect(@user.audits.last.action).to eq("update")
361
+ expect(Audited::Audit.updates.order(:id).last).to eq(@user.audits.last)
362
+ expect(@user.audits.updates.last).to eq(@user.audits.last)
363
+ end
364
+
365
+ it "should store the changed attributes" do
366
+ @user.update! name: "Changed"
367
+ expect(@user.audits.last.audited_changes).to eq({"name" => ["Brandon", "Changed"]})
368
+ end
369
+
370
+ it "should store changed enum values" do
371
+ @user.update! status: 1
372
+ expect(@user.audits.last.audited_changes["status"]).to eq([0, 1])
373
+ end
374
+
375
+ it "should store audit comment" do
376
+ expect(@user.audits.last.comment).to eq("Update")
377
+ end
378
+
379
+ it "should not save an audit if only specified on create/destroy" do
380
+ on_create_destroy = Models::ActiveRecord::OnCreateDestroy.create(name: "Bart")
381
+ expect {
382
+ on_create_destroy.update! name: "Changed"
383
+ }.to_not change(Audited::Audit, :count)
384
+ end
385
+
386
+ it "should not save an audit if the value doesn't change after type casting" do
387
+ @user.update! logins: 0, activated: true
388
+ expect { @user.update_attribute :logins, "0" }.to_not change(Audited::Audit, :count)
389
+ expect { @user.update_attribute :activated, 1 }.to_not change(Audited::Audit, :count)
390
+ expect { @user.update_attribute :activated, "1" }.to_not change(Audited::Audit, :count)
391
+ end
392
+
393
+ describe "with no dirty changes" do
394
+ it "does not create an audit if the record is not changed" do
395
+ expect {
396
+ @user.save!
397
+ }.to_not change(Audited::Audit, :count)
398
+ end
399
+
400
+ it "creates an audit when an audit comment is present" do
401
+ expect {
402
+ @user.audit_comment = "Comment"
403
+ @user.save!
404
+ }.to change(Audited::Audit, :count)
405
+ end
406
+ end
407
+ end
408
+
409
+ describe "on destroy" do
410
+ before do
411
+ @user = create_user(status: :active)
412
+ end
413
+
414
+ it "should save an audit" do
415
+ expect {
416
+ @user.destroy
417
+ }.to change(Audited::Audit, :count)
418
+
419
+ expect(@user.audits.size).to eq(2)
420
+ end
421
+
422
+ it "should set the action to 'destroy'" do
423
+ @user.destroy
424
+
425
+ expect(@user.audits.last.action).to eq("destroy")
426
+ expect(Audited::Audit.destroys.order(:id).last).to eq(@user.audits.last)
427
+ expect(@user.audits.destroys.last).to eq(@user.audits.last)
428
+ end
429
+
430
+ it "should store all of the audited attributes" do
431
+ @user.destroy
432
+
433
+ expect(@user.audits.last.audited_changes).to eq(@user.audited_attributes)
434
+ end
435
+
436
+ it "should store enum value" do
437
+ @user.destroy
438
+ expect(@user.audits.last.audited_changes["status"]).to eq(0)
439
+ end
440
+
441
+ it "should be able to reconstruct a destroyed record without history" do
442
+ @user.audits.delete_all
443
+ @user.destroy
444
+
445
+ revision = @user.audits.first.revision
446
+ expect(revision.name).to eq(@user.name)
447
+ end
448
+
449
+ it "should not save an audit if only specified on create/update" do
450
+ on_create_update = Models::ActiveRecord::OnCreateUpdate.create!(name: "Bart")
451
+
452
+ expect {
453
+ on_create_update.destroy
454
+ }.to_not change(Audited::Audit, :count)
455
+ end
456
+
457
+ it "should audit dependent destructions" do
458
+ owner = Models::ActiveRecord::Owner.create!
459
+ company = owner.companies.create!
460
+
461
+ expect {
462
+ owner.destroy
463
+ }.to change(Audited::Audit, :count)
464
+
465
+ expect(company.audits.map { |a| a.action }).to eq(["create", "destroy"])
466
+ end
467
+ end
468
+
469
+ describe "on destroy with unsaved object" do
470
+ let(:user) { Models::ActiveRecord::User.new }
471
+
472
+ it "should not audit on 'destroy'" do
473
+ expect {
474
+ user.destroy
475
+ }.to_not raise_error
476
+
477
+ expect(user.audits).to be_empty
478
+ end
479
+ end
480
+
481
+ describe "associated with" do
482
+ let(:owner) { Models::ActiveRecord::Owner.create(name: "Models::ActiveRecord::Owner") }
483
+ let(:owned_company) { Models::ActiveRecord::OwnedCompany.create!(name: "The auditors", owner: owner) }
484
+
485
+ it "should record the associated object on create" do
486
+ expect(owned_company.audits.first.associated).to eq(owner)
487
+ end
488
+
489
+ it "should store the associated object on update" do
490
+ owned_company.update_attribute(:name, "The Auditors")
491
+ expect(owned_company.audits.last.associated).to eq(owner)
492
+ end
493
+
494
+ it "should store the associated object on destroy" do
495
+ owned_company.destroy
496
+ expect(owned_company.audits.last.associated).to eq(owner)
497
+ end
498
+ end
499
+
500
+ describe "has associated audits" do
501
+ let!(:owner) { Models::ActiveRecord::Owner.create!(name: "Models::ActiveRecord::Owner") }
502
+ let!(:owned_company) { Models::ActiveRecord::OwnedCompany.create!(name: "The auditors", owner: owner) }
503
+
504
+ it "should list the associated audits" do
505
+ expect(owner.associated_audits.length).to eq(1)
506
+ expect(owner.associated_audits.first.auditable).to eq(owned_company)
507
+ end
508
+ end
509
+
510
+ describe "max_audits" do
511
+ it "should respect global setting" do
512
+ stub_global_max_audits(10) do
513
+ expect(Models::ActiveRecord::User.audited_options[:max_audits]).to eq(10)
514
+ end
515
+ end
516
+
517
+ it "should respect per model setting" do
518
+ stub_global_max_audits(10) do
519
+ expect(Models::ActiveRecord::MaxAuditsUser.audited_options[:max_audits]).to eq(5)
520
+ end
521
+ end
522
+
523
+ it "should delete old audits when keeped amount exceeded" do
524
+ stub_global_max_audits(2) do
525
+ user = create_versions(2)
526
+ user.update(name: "John")
527
+ expect(user.audits.pluck(:version)).to eq([2, 3])
528
+ end
529
+ end
530
+
531
+ it "should not delete old audits when keeped amount not exceeded" do
532
+ stub_global_max_audits(3) do
533
+ user = create_versions(2)
534
+ user.update(name: "John")
535
+ expect(user.audits.pluck(:version)).to eq([1, 2, 3])
536
+ end
537
+ end
538
+
539
+ it "should delete old extra audits after introducing limit" do
540
+ stub_global_max_audits(nil) do
541
+ user = Models::ActiveRecord::User.create!(name: "Brandon", username: "brandon")
542
+ user.update!(name: "Foobar")
543
+ user.update!(name: "Awesome", username: "keepers")
544
+ user.update!(activated: true)
545
+
546
+ Audited.max_audits = 3
547
+ Models::ActiveRecord::User.send(:normalize_audited_options)
548
+ user.update!(favourite_device: "Android Phone")
549
+ audits = user.audits
550
+
551
+ expect(audits.count).to eq(3)
552
+ expect(audits[0].audited_changes).to include({"name" => ["Foobar", "Awesome"], "username" => ["brandon", "keepers"]})
553
+ expect(audits[1].audited_changes).to eq({"activated" => [nil, true]})
554
+ expect(audits[2].audited_changes).to eq({"favourite_device" => [nil, "Android Phone"]})
555
+ end
556
+ end
557
+
558
+ it "should add comment line for combined audit" do
559
+ stub_global_max_audits(2) do
560
+ user = Models::ActiveRecord::User.create!(name: "Foobar 1")
561
+ user.update(name: "Foobar 2", audit_comment: "First audit comment")
562
+ user.update(name: "Foobar 3", audit_comment: "Second audit comment")
563
+ expect(user.audits.first.comment).to match(/First audit comment.+is the result of multiple/m)
564
+ end
565
+ end
566
+
567
+ def stub_global_max_audits(max_audits)
568
+ previous_max_audits = Audited.max_audits
569
+ previous_user_audited_options = Models::ActiveRecord::User.audited_options.dup
570
+ begin
571
+ Audited.max_audits = max_audits
572
+ Models::ActiveRecord::User.send(:normalize_audited_options) # reloads audited_options
573
+ yield
574
+ ensure
575
+ Audited.max_audits = previous_max_audits
576
+ Models::ActiveRecord::User.audited_options = previous_user_audited_options
577
+ end
578
+ end
579
+ end
580
+
581
+ describe "revisions" do
582
+ let(:user) { create_versions }
583
+
584
+ it "should return an Array of Users" do
585
+ expect(user.revisions).to be_a_kind_of(Array)
586
+ user.revisions.each { |version| expect(version).to be_a_kind_of Models::ActiveRecord::User }
587
+ end
588
+
589
+ it "should have one revision for a new record" do
590
+ expect(create_user.revisions.size).to eq(1)
591
+ end
592
+
593
+ it "should have one revision for each audit" do
594
+ expect(user.audits.size).to eql(user.revisions.size)
595
+ end
596
+
597
+ it "should set the attributes for each revision" do
598
+ u = Models::ActiveRecord::User.create(name: "Brandon", username: "brandon")
599
+ u.update! name: "Foobar"
600
+ u.update! name: "Awesome", username: "keepers"
601
+
602
+ expect(u.revisions.size).to eql(3)
603
+
604
+ expect(u.revisions[0].name).to eql("Brandon")
605
+ expect(u.revisions[0].username).to eql("brandon")
606
+
607
+ expect(u.revisions[1].name).to eql("Foobar")
608
+ expect(u.revisions[1].username).to eql("brandon")
609
+
610
+ expect(u.revisions[2].name).to eql("Awesome")
611
+ expect(u.revisions[2].username).to eql("keepers")
612
+ end
613
+
614
+ it "access to only recent revisions" do
615
+ u = Models::ActiveRecord::User.create(name: "Brandon", username: "brandon")
616
+ u.update! name: "Foobar"
617
+ u.update! name: "Awesome", username: "keepers"
618
+
619
+ expect(u.revisions(2).size).to eq(2)
620
+
621
+ expect(u.revisions(2)[0].name).to eq("Foobar")
622
+ expect(u.revisions(2)[0].username).to eq("brandon")
623
+
624
+ expect(u.revisions(2)[1].name).to eq("Awesome")
625
+ expect(u.revisions(2)[1].username).to eq("keepers")
626
+ end
627
+
628
+ it "should be empty if no audits exist" do
629
+ user.audits.delete_all
630
+ expect(user.revisions).to be_empty
631
+ end
632
+
633
+ it "should ignore attributes that have been deleted" do
634
+ user.audits.last.update! audited_changes: {old_attribute: "old value"}
635
+ expect { user.revisions }.to_not raise_error
636
+ end
637
+ end
638
+
639
+ describe "revisions" do
640
+ let(:user) { create_versions(5) }
641
+
642
+ it "should maintain identity" do
643
+ expect(user.revision(1)).to eq(user)
644
+ end
645
+
646
+ it "should find the given revision" do
647
+ revision = user.revision(3)
648
+ expect(revision).to be_a_kind_of(Models::ActiveRecord::User)
649
+ expect(revision.audit_version).to eq(3)
650
+ expect(revision.name).to eq("Foobar 3")
651
+ end
652
+
653
+ it "should find the previous revision with :previous" do
654
+ revision = user.revision(:previous)
655
+ expect(revision.audit_version).to eq(4)
656
+ # expect(revision).to eq(user.revision(4))
657
+ expect(revision.attributes).to eq(user.revision(4).attributes)
658
+ end
659
+
660
+ it "should be able to get the previous revision repeatedly" do
661
+ previous = user.revision(:previous)
662
+ expect(previous.audit_version).to eq(4)
663
+ expect(previous.revision(:previous).audit_version).to eq(3)
664
+ end
665
+
666
+ it "should be able to set protected attributes" do
667
+ u = Models::ActiveRecord::User.create(name: "Brandon")
668
+ u.update_attribute :logins, 1
669
+ u.update_attribute :logins, 2
670
+
671
+ expect(u.revision(3).logins).to eq(2)
672
+ expect(u.revision(2).logins).to eq(1)
673
+ expect(u.revision(1).logins).to eq(0)
674
+ end
675
+
676
+ it "should set attributes directly" do
677
+ u = Models::ActiveRecord::User.create(name: "<Joe>")
678
+ expect(u.revision(1).name).to eq("&lt;Joe&gt;")
679
+ end
680
+
681
+ it "should set the attributes for each revision" do
682
+ u = Models::ActiveRecord::User.create(name: "Brandon", username: "brandon")
683
+ u.update! name: "Foobar"
684
+ u.update! name: "Awesome", username: "keepers"
685
+
686
+ expect(u.revision(3).name).to eq("Awesome")
687
+ expect(u.revision(3).username).to eq("keepers")
688
+
689
+ expect(u.revision(2).name).to eq("Foobar")
690
+ expect(u.revision(2).username).to eq("brandon")
691
+
692
+ expect(u.revision(1).name).to eq("Brandon")
693
+ expect(u.revision(1).username).to eq("brandon")
694
+ end
695
+
696
+ it "should correctly restore revision with enum" do
697
+ u = Models::ActiveRecord::User.create(status: :active)
698
+ u.update_attribute(:status, :reliable)
699
+ u.update_attribute(:status, :banned)
700
+
701
+ expect(u.revision(3)).to be_banned
702
+ expect(u.revision(2)).to be_reliable
703
+ expect(u.revision(1)).to be_active
704
+ end
705
+
706
+ it "should be able to get time for first revision" do
707
+ suspended_at = Time.zone.now
708
+ u = Models::ActiveRecord::User.create(suspended_at: suspended_at)
709
+ expect(u.revision(1).suspended_at.to_s).to eq(suspended_at.to_s)
710
+ end
711
+
712
+ it "should not raise an error when no previous audits exist" do
713
+ user.audits.destroy_all
714
+ expect { user.revision(:previous) }.to_not raise_error
715
+ end
716
+
717
+ it "should mark revision's attributes as changed" do
718
+ expect(user.revision(1).name_changed?).to eq(true)
719
+ end
720
+
721
+ it "should record new audit when saving revision" do
722
+ expect {
723
+ user.revision(1).save!
724
+ }.to change(user.audits, :count).by(1)
725
+ end
726
+
727
+ it "should re-insert destroyed records" do
728
+ user.destroy
729
+ expect {
730
+ user.revision(1).save!
731
+ }.to change(Models::ActiveRecord::User, :count).by(1)
732
+ end
733
+
734
+ it "should return nil for values greater than the number of revisions" do
735
+ expect(user.revision(user.revisions.count + 1)).to be_nil
736
+ end
737
+
738
+ it "should work with array attributes" do
739
+ u = Models::ActiveRecord::User.create!(phone_numbers: ["+1 800-444-4444"])
740
+ u.update!(phone_numbers: ["+1 804-222-1111", "+1 317 222-2222"])
741
+
742
+ expect(u.revision(0).phone_numbers).to eq(["+1 804-222-1111", "+1 317 222-2222"])
743
+ expect(u.revision(1).phone_numbers).to eq(["+1 800-444-4444"])
744
+ end
745
+ end
746
+
747
+ describe "revision_at" do
748
+ let(:user) { create_user }
749
+
750
+ it "should find the latest revision before the given time" do
751
+ audit = user.audits.first
752
+ audit.created_at = 1.hour.ago
753
+ audit.save!
754
+ user.update! name: "updated"
755
+ expect(user.revision_at(2.minutes.ago).audit_version).to eq(1)
756
+ end
757
+
758
+ it "should be nil if given a time before audits" do
759
+ expect(user.revision_at(1.week.ago)).to be_nil
760
+ end
761
+ end
762
+
763
+ describe "own_and_associated_audits" do
764
+ it "should return audits for self and associated audits" do
765
+ owner = Models::ActiveRecord::Owner.create!
766
+ company = owner.companies.create!
767
+ company.update!(name: "Collective Idea")
768
+
769
+ other_owner = Models::ActiveRecord::Owner.create!
770
+ other_owner.companies.create!
771
+
772
+ expect(owner.own_and_associated_audits).to match_array(owner.audits + company.audits)
773
+ end
774
+
775
+ it "should return audits for STI classes" do
776
+ # Where parent is STI
777
+ sti_company = Models::ActiveRecord::Company::STICompany.create!
778
+ sti_company.update!(name: "Collective Idea")
779
+ expect(sti_company.own_and_associated_audits).to match_array(sti_company.audits)
780
+
781
+ # Where associated is STI
782
+ owner = Models::ActiveRecord::Owner.create!
783
+ company = owner.companies.create! type: "Models::ActiveRecord::OwnedCompany::STICompany"
784
+ company.update!(name: "Collective Idea")
785
+ expect(owner.own_and_associated_audits).to match_array(owner.audits + company.audits)
786
+ end
787
+
788
+ it "should order audits by creation time" do
789
+ owner = Models::ActiveRecord::Owner.create!
790
+ first_audit = owner.audits.first
791
+ first_audit.update_column(:created_at, 1.year.ago)
792
+
793
+ company = owner.companies.create!
794
+ second_audit = company.audits.first
795
+ second_audit.update_column(:created_at, 1.month.ago)
796
+
797
+ company.update!(name: "Collective Idea")
798
+ third_audit = company.audits.last
799
+ expect(owner.own_and_associated_audits.to_a).to eq([third_audit, second_audit, first_audit])
800
+ end
801
+ end
802
+
803
+ describe "without auditing" do
804
+ it "should not save an audit when calling #save_without_auditing" do
805
+ expect {
806
+ u = Models::ActiveRecord::User.new(name: "Brandon")
807
+ expect(u.save_without_auditing).to eq(true)
808
+ }.to_not change(Audited::Audit, :count)
809
+ end
810
+
811
+ it "should not save an audit inside of the #without_auditing block" do
812
+ expect {
813
+ Models::ActiveRecord::User.without_auditing { Models::ActiveRecord::User.create!(name: "Brandon") }
814
+ }.to_not change(Audited::Audit, :count)
815
+ end
816
+
817
+ it "should reset auditing status even it raises an exception" do
818
+ begin
819
+ Models::ActiveRecord::User.without_auditing { raise }
820
+ rescue
821
+ nil
822
+ end
823
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
824
+ end
825
+
826
+ it "should be thread safe using a #without_auditing block" do
827
+ skip if Models::ActiveRecord::User.connection.class.name.include?("SQLite")
828
+
829
+ t1 = Thread.new do
830
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
831
+ Models::ActiveRecord::User.without_auditing do
832
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
833
+ Models::ActiveRecord::User.create!(name: "Bart")
834
+ sleep 1
835
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
836
+ end
837
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
838
+ end
839
+
840
+ t2 = Thread.new do
841
+ sleep 0.5
842
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
843
+ Models::ActiveRecord::User.create!(name: "Lisa")
844
+ end
845
+ t1.join
846
+ t2.join
847
+
848
+ expect(Models::ActiveRecord::User.find_by_name("Bart").audits.count).to eq(0)
849
+ expect(Models::ActiveRecord::User.find_by_name("Lisa").audits.count).to eq(1)
850
+ end
851
+
852
+ it "should not save an audit when auditing is globally disabled" do
853
+ expect(Audited.auditing_enabled).to eq(true)
854
+ Audited.auditing_enabled = false
855
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
856
+
857
+ user = create_user
858
+ expect(user.audits.count).to eq(0)
859
+
860
+ Audited.auditing_enabled = true
861
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
862
+
863
+ user.update!(name: "Test")
864
+ expect(user.audits.count).to eq(1)
865
+ Models::ActiveRecord::User.enable_auditing
866
+ end
867
+ end
868
+
869
+ describe "with auditing" do
870
+ it "should save an audit when calling #save_with_auditing" do
871
+ expect {
872
+ u = Models::ActiveRecord::User.new(name: "Brandon")
873
+ Models::ActiveRecord::User.auditing_enabled = false
874
+ expect(u.save_with_auditing).to eq(true)
875
+ Models::ActiveRecord::User.auditing_enabled = true
876
+ }.to change(Audited::Audit, :count).by(1)
877
+ end
878
+
879
+ it "should save an audit inside of the #with_auditing block" do
880
+ expect {
881
+ Models::ActiveRecord::User.auditing_enabled = false
882
+ Models::ActiveRecord::User.with_auditing { Models::ActiveRecord::User.create!(name: "Brandon") }
883
+ Models::ActiveRecord::User.auditing_enabled = true
884
+ }.to change(Audited::Audit, :count).by(1)
885
+ end
886
+
887
+ it "should reset auditing status even it raises an exception" do
888
+ Models::ActiveRecord::User.disable_auditing
889
+ begin
890
+ Models::ActiveRecord::User.with_auditing { raise }
891
+ rescue
892
+ nil
893
+ end
894
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
895
+ Models::ActiveRecord::User.enable_auditing
896
+ end
897
+
898
+ it "should be thread safe using a #with_auditing block" do
899
+ skip if Models::ActiveRecord::User.connection.class.name.include?("SQLite")
900
+
901
+ t1 = Thread.new do
902
+ Models::ActiveRecord::User.disable_auditing
903
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
904
+ Models::ActiveRecord::User.with_auditing do
905
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
906
+
907
+ Models::ActiveRecord::User.create!(name: "Shaggy")
908
+ sleep 1
909
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(true)
910
+ end
911
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
912
+ Models::ActiveRecord::User.enable_auditing
913
+ end
914
+
915
+ t2 = Thread.new do
916
+ sleep 0.5
917
+ Models::ActiveRecord::User.disable_auditing
918
+ expect(Models::ActiveRecord::User.auditing_enabled).to eq(false)
919
+ Models::ActiveRecord::User.create!(name: "Scooby")
920
+ Models::ActiveRecord::User.enable_auditing
921
+ end
922
+ t1.join
923
+ t2.join
924
+
925
+ Models::ActiveRecord::User.enable_auditing
926
+ expect(Models::ActiveRecord::User.find_by_name("Shaggy").audits.count).to eq(1)
927
+ expect(Models::ActiveRecord::User.find_by_name("Scooby").audits.count).to eq(0)
928
+ end
929
+ end
930
+
931
+ describe "comment required" do
932
+ describe "on create" do
933
+ it "should not validate when audit_comment is not supplied when initialized" do
934
+ expect(Models::ActiveRecord::CommentRequiredUser.new(name: "Foo")).not_to be_valid
935
+ end
936
+
937
+ it "should not validate when audit_comment is not supplied trying to create" do
938
+ expect(Models::ActiveRecord::CommentRequiredUser.create(name: "Foo")).not_to be_valid
939
+ end
940
+
941
+ it "should validate when audit_comment is supplied" do
942
+ expect(Models::ActiveRecord::CommentRequiredUser.create(name: "Foo", audit_comment: "Create")).to be_valid
943
+ end
944
+
945
+ it "should validate when audit_comment is not supplied, and creating is not being audited" do
946
+ expect(Models::ActiveRecord::OnUpdateCommentRequiredUser.create(name: "Foo")).to be_valid
947
+ expect(Models::ActiveRecord::OnDestroyCommentRequiredUser.create(name: "Foo")).to be_valid
948
+ end
949
+
950
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
951
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
952
+ expect(Models::ActiveRecord::CommentRequiredUser.create(name: "Foo")).to be_valid
953
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
954
+ end
955
+
956
+ it "should validate when audit_comment is not supplied, and only excluded attributes changed" do
957
+ expect(Models::ActiveRecord::CommentRequiredUser.new(password: "Foo")).to be_valid
958
+ end
959
+ end
960
+
961
+ describe "on update" do
962
+ let(:user) { Models::ActiveRecord::CommentRequiredUser.create!(audit_comment: "Create") }
963
+ let(:on_create_user) { Models::ActiveRecord::OnDestroyCommentRequiredUser.create }
964
+ let(:on_destroy_user) { Models::ActiveRecord::OnDestroyCommentRequiredUser.create }
965
+
966
+ it "should not validate when audit_comment is not supplied" do
967
+ expect(user.update(name: "Test")).to eq(false)
968
+ end
969
+
970
+ it "should validate when audit_comment is not supplied, and updating is not being audited" do
971
+ expect(on_create_user.update(name: "Test")).to eq(true)
972
+ expect(on_destroy_user.update(name: "Test")).to eq(true)
973
+ end
974
+
975
+ it "should validate when audit_comment is supplied" do
976
+ expect(user.update(name: "Test", audit_comment: "Update")).to eq(true)
977
+ end
978
+
979
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
980
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
981
+ expect(user.update(name: "Test")).to eq(true)
982
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
983
+ end
984
+
985
+ it "should validate when audit_comment is not supplied, and only excluded attributes changed" do
986
+ expect(user.update(password: "Test")).to eq(true)
987
+ end
988
+ end
989
+
990
+ describe "on destroy" do
991
+ let(:user) { Models::ActiveRecord::CommentRequiredUser.create!(audit_comment: "Create") }
992
+ let(:on_create_user) { Models::ActiveRecord::OnCreateCommentRequiredUser.create!(audit_comment: "Create") }
993
+ let(:on_update_user) { Models::ActiveRecord::OnUpdateCommentRequiredUser.create }
994
+
995
+ it "should not validate when audit_comment is not supplied" do
996
+ expect(user.destroy).to eq(false)
997
+ end
998
+
999
+ it "should validate when audit_comment is supplied" do
1000
+ user.audit_comment = "Destroy"
1001
+ expect(user.destroy).to eq(user)
1002
+ end
1003
+
1004
+ it "should validate when audit_comment is not supplied, and destroying is not being audited" do
1005
+ expect(on_create_user.destroy).to eq(on_create_user)
1006
+ expect(on_update_user.destroy).to eq(on_update_user)
1007
+ end
1008
+
1009
+ it "should validate when audit_comment is not supplied, and auditing is disabled" do
1010
+ Models::ActiveRecord::CommentRequiredUser.disable_auditing
1011
+ expect(user.destroy).to eq(user)
1012
+ Models::ActiveRecord::CommentRequiredUser.enable_auditing
1013
+ end
1014
+ end
1015
+ end
1016
+
1017
+ describe "no update with comment only" do
1018
+ let(:user) { Models::ActiveRecord::NoUpdateWithCommentOnlyUser.create }
1019
+
1020
+ it "does not create an audit when only an audit_comment is present" do
1021
+ user.audit_comment = "Comment"
1022
+ expect { user.save! }.to_not change(Audited::Audit, :count)
1023
+ end
1024
+ end
1025
+
1026
+ describe "attr_protected and attr_accessible" do
1027
+ it "should not raise error when attr_accessible is set and protected is false" do
1028
+ expect {
1029
+ Models::ActiveRecord::AccessibleAfterDeclarationUser.new(name: "No fail!")
1030
+ }.to_not raise_error
1031
+ end
1032
+
1033
+ it "should not rause an error when attr_accessible is declared before audited" do
1034
+ expect {
1035
+ Models::ActiveRecord::AccessibleAfterDeclarationUser.new(name: "No fail!")
1036
+ }.to_not raise_error
1037
+ end
1038
+ end
1039
+
1040
+ describe "audit_as" do
1041
+ let(:user) { Models::ActiveRecord::User.create name: "Testing" }
1042
+
1043
+ it "should record user objects" do
1044
+ Models::ActiveRecord::Company.audit_as(user) do
1045
+ company = Models::ActiveRecord::Company.create name: "The auditors"
1046
+ company.update! name: "The Auditors"
1047
+
1048
+ company.audits.each do |audit|
1049
+ expect(audit.user).to eq(user)
1050
+ end
1051
+ end
1052
+ end
1053
+
1054
+ it "should record usernames" do
1055
+ Models::ActiveRecord::Company.audit_as(user.name) do
1056
+ company = Models::ActiveRecord::Company.create name: "The auditors"
1057
+ company.update! name: "The Auditors"
1058
+
1059
+ company.audits.each do |audit|
1060
+ expect(audit.user).to eq(user.name)
1061
+ end
1062
+ end
1063
+ end
1064
+ end
1065
+
1066
+ describe "after_audit" do
1067
+ let(:user) { Models::ActiveRecord::UserWithAfterAudit.new }
1068
+
1069
+ it "should invoke after_audit callback on create" do
1070
+ expect(user.bogus_attr).to be_nil
1071
+ expect(user.save).to eq(true)
1072
+ expect(user.bogus_attr).to eq("do something")
1073
+ end
1074
+ end
1075
+
1076
+ describe "around_audit" do
1077
+ let(:user) { Models::ActiveRecord::UserWithAfterAudit.new }
1078
+
1079
+ it "should invoke around_audit callback on create" do
1080
+ expect(user.around_attr).to be_nil
1081
+ expect(user.save).to eq(true)
1082
+ expect(user.around_attr).to eq(user.audits.last)
1083
+ end
1084
+ end
1085
+
1086
+ describe "STI auditing" do
1087
+ it "should correctly disable auditing when using STI" do
1088
+ company = Models::ActiveRecord::Company::STICompany.create name: "The auditors"
1089
+ expect(company.type).to eq("Models::ActiveRecord::Company::STICompany")
1090
+ expect {
1091
+ Models::ActiveRecord::Company.auditing_enabled = false
1092
+ company.update! name: "STI auditors"
1093
+ Models::ActiveRecord::Company.auditing_enabled = true
1094
+ }.to_not change(Audited::Audit, :count)
1095
+ end
1096
+ end
1097
+ end