active_snapshot 0.1.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 +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +145 -0
- data/Rakefile +52 -0
- data/lib/active_snapshot.rb +17 -0
- data/lib/active_snapshot/models/concerns/snapshots_concern.rb +111 -0
- data/lib/active_snapshot/models/snapshot.rb +104 -0
- data/lib/active_snapshot/models/snapshot_item.rb +40 -0
- data/lib/active_snapshot/version.rb +3 -0
- data/lib/generators/active_snapshot/install/install_generator.rb +22 -0
- data/lib/generators/active_snapshot/install/templates/create_snapshots_tables.rb.erb +21 -0
- data/lib/generators/active_snapshot/migration_generator.rb +76 -0
- data/test/dummy_app/Rakefile +7 -0
- data/test/dummy_app/app/assets/config/manifest.js +3 -0
- data/test/dummy_app/app/assets/javascripts/application.js +0 -0
- data/test/dummy_app/app/assets/stylesheets/application.css +3 -0
- data/test/dummy_app/app/controllers/application_controller.rb +3 -0
- data/test/dummy_app/app/models/application_record.rb +3 -0
- data/test/dummy_app/app/models/comment.rb +5 -0
- data/test/dummy_app/app/models/post.rb +13 -0
- data/test/dummy_app/app/models/volatile_post.rb +5 -0
- data/test/dummy_app/app/views/layouts/application.html.erb +14 -0
- data/test/dummy_app/config.ru +4 -0
- data/test/dummy_app/config/application.rb +61 -0
- data/test/dummy_app/config/boot.rb +10 -0
- data/test/dummy_app/config/database.yml +20 -0
- data/test/dummy_app/config/environment.rb +5 -0
- data/test/dummy_app/config/environments/development.rb +30 -0
- data/test/dummy_app/config/environments/production.rb +60 -0
- data/test/dummy_app/config/environments/test.rb +41 -0
- data/test/dummy_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy_app/config/initializers/inflections.rb +10 -0
- data/test/dummy_app/config/initializers/mime_types.rb +5 -0
- data/test/dummy_app/config/initializers/secret_token.rb +11 -0
- data/test/dummy_app/config/initializers/session_store.rb +8 -0
- data/test/dummy_app/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy_app/config/locales/en.yml +5 -0
- data/test/dummy_app/config/routes.rb +6 -0
- data/test/dummy_app/config/secrets.yml +22 -0
- data/test/dummy_app/db/migrate/20210128155312_set_up_test_tables.rb +19 -0
- data/test/dummy_app/db/migrate/20210306070749_create_snapshots_tables.rb +21 -0
- data/test/dummy_app/log/development.log +9 -0
- data/test/dummy_app/log/test.log +46812 -0
- data/test/generators/active_snapshot/install_generator_test.rb +31 -0
- data/test/models/snapshot_item_test.rb +75 -0
- data/test/models/snapshot_test.rb +109 -0
- data/test/models/snapshots_concern_test.rb +111 -0
- data/test/test_helper.rb +71 -0
- data/test/unit/active_snapshot_test.rb +60 -0
- data/test/unit/errors_test.rb +12 -0
- metadata +231 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require File.expand_path("../../../lib/generators/active_snapshot/install/install_generator", __dir__)
|
3
|
+
|
4
|
+
class InstallGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests ActiveSnapshot::InstallGenerator
|
6
|
+
destination File.expand_path("tmp", __dir__)
|
7
|
+
|
8
|
+
setup do
|
9
|
+
prepare_destination # cleanup the tmp directory
|
10
|
+
run_generator
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
### Disable during debugging
|
15
|
+
prepare_destination # cleanup the tmp directory
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_should_add_migration
|
19
|
+
run_generator
|
20
|
+
|
21
|
+
relative_path = "db/migrate/create_snapshots_tables.rb"
|
22
|
+
|
23
|
+
assert_migration(relative_path) do |content|
|
24
|
+
assert_match(/create_table :snapshots/, content)
|
25
|
+
assert_match(/create_table :snapshot_items/, content)
|
26
|
+
end
|
27
|
+
|
28
|
+
### Test for syntax errors in file
|
29
|
+
require send(:migration_file_name, relative_path)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SnapshotItemTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@snapshot_klass = ActiveSnapshot::Snapshot
|
7
|
+
@snapshot_item_klass = ActiveSnapshot::SnapshotItem
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_relationships
|
14
|
+
instance = @snapshot_item_klass.new
|
15
|
+
|
16
|
+
assert instance.snapshot.nil?
|
17
|
+
assert instance.item.nil?
|
18
|
+
|
19
|
+
assert_raises do
|
20
|
+
instance.snapshot = instance
|
21
|
+
end
|
22
|
+
|
23
|
+
instance.snapshot = ActiveSnapshot::Snapshot.new
|
24
|
+
|
25
|
+
instance.item = instance
|
26
|
+
|
27
|
+
assert_not instance.snapshot.nil?
|
28
|
+
assert_not instance.item.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_validations
|
32
|
+
instance = @snapshot_item_klass.new
|
33
|
+
|
34
|
+
instance.valid?
|
35
|
+
|
36
|
+
[:item_id, :item_type, :snapshot_id].each do |attr|
|
37
|
+
assert instance.errors[attr].present? ### presence error
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_post = DATA[:shared_post]
|
41
|
+
snapshot = shared_post.snapshots.first
|
42
|
+
|
43
|
+
instance = @snapshot_item_klass.new(item: snapshot.item, snapshot: snapshot)
|
44
|
+
|
45
|
+
instance.valid?
|
46
|
+
|
47
|
+
assert instance.errors[:item_id].present? ### uniq error
|
48
|
+
assert instance.errors[:item_type].present? ### uniq error
|
49
|
+
|
50
|
+
instance = @snapshot_item_klass.new(item_id: 1, item_type: 'Foobar', snapshot: snapshot)
|
51
|
+
|
52
|
+
assert instance.valid?
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_object
|
56
|
+
@snapshot = @snapshot_klass.includes(:snapshot_items).first
|
57
|
+
|
58
|
+
@snapshot_item = @snapshot.snapshot_items.first
|
59
|
+
|
60
|
+
assert @snapshot_item.object.is_a?(HashWithIndifferentAccess)
|
61
|
+
|
62
|
+
@snapshot_item.object = {foo: :bar}
|
63
|
+
|
64
|
+
assert 'bar', @snapshot_item.object['foo']
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_restore_item!
|
68
|
+
@snapshot = @snapshot_klass.includes(:snapshot_items).first
|
69
|
+
|
70
|
+
@snapshot_item = @snapshot.snapshot_items.first
|
71
|
+
|
72
|
+
@snapshot_item.restore_item!
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SnapshotTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@snapshot_klass = ActiveSnapshot::Snapshot
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_relationships
|
13
|
+
shared_post = DATA[:shared_post]
|
14
|
+
|
15
|
+
instance = @snapshot_klass.new
|
16
|
+
|
17
|
+
assert instance.user.nil?
|
18
|
+
assert instance.item.nil?
|
19
|
+
assert instance.snapshot_items.empty?
|
20
|
+
|
21
|
+
instance.user = instance
|
22
|
+
instance.item = instance
|
23
|
+
|
24
|
+
assert_raises do
|
25
|
+
instance.snapshot_items << instance
|
26
|
+
end
|
27
|
+
|
28
|
+
instance.snapshot_items << ActiveSnapshot::SnapshotItem.new
|
29
|
+
|
30
|
+
assert_not instance.user.nil?
|
31
|
+
assert_not instance.item.nil?
|
32
|
+
assert_not instance.snapshot_items.empty?
|
33
|
+
|
34
|
+
instance = @snapshot_klass.new(item: shared_post, user: shared_post)
|
35
|
+
|
36
|
+
assert instance.item.id, shared_post.id
|
37
|
+
assert instance.user.id, shared_post.id
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_validations
|
41
|
+
shared_post = DATA[:shared_post]
|
42
|
+
snapshot = shared_post.snapshots.first
|
43
|
+
|
44
|
+
instance = @snapshot_klass.new
|
45
|
+
|
46
|
+
instance.valid?
|
47
|
+
|
48
|
+
[:item_id, :item_type, :identifier].each do |attr|
|
49
|
+
assert instance.errors[attr].present? ### presence error
|
50
|
+
end
|
51
|
+
|
52
|
+
instance = @snapshot_klass.new(item: snapshot.item, identifier: snapshot.identifier)
|
53
|
+
|
54
|
+
instance.valid?
|
55
|
+
|
56
|
+
assert instance.errors[:identifier].present? ### uniq error
|
57
|
+
|
58
|
+
instance = @snapshot_klass.new(item: snapshot.item, identifier: 'random')
|
59
|
+
|
60
|
+
assert instance.valid?
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_metadata
|
64
|
+
@snapshot = @snapshot_klass.first
|
65
|
+
|
66
|
+
assert @snapshot.metadata.is_a?(HashWithIndifferentAccess)
|
67
|
+
|
68
|
+
@snapshot.metadata = {foo: :bar}
|
69
|
+
|
70
|
+
assert_equal 'bar', @snapshot.metadata['foo']
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_build_snapshot_item
|
74
|
+
@snapshot = @snapshot_klass.first
|
75
|
+
|
76
|
+
snapshot_item = @snapshot.build_snapshot_item(Post.first)
|
77
|
+
|
78
|
+
assert snapshot_item.is_a?(ActiveSnapshot::SnapshotItem)
|
79
|
+
|
80
|
+
assert snapshot_item.new_record?
|
81
|
+
|
82
|
+
assert_equal @snapshot.id, snapshot_item.snapshot_id
|
83
|
+
|
84
|
+
@snapshot.build_snapshot_item(Post.first, child_group_name: :foobar)
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_restore
|
88
|
+
@snapshot = @snapshot_klass.first
|
89
|
+
|
90
|
+
@snapshot.restore!
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_fetch_reified_items
|
94
|
+
@snapshot = @snapshot_klass.first
|
95
|
+
|
96
|
+
reified_items = @snapshot.fetch_reified_items
|
97
|
+
|
98
|
+
assert reified_items.is_a?(Array)
|
99
|
+
|
100
|
+
assert reified_items.first.readonly?
|
101
|
+
|
102
|
+
children_hash = reified_items.last
|
103
|
+
|
104
|
+
assert children_hash.is_a?(HashWithIndifferentAccess)
|
105
|
+
|
106
|
+
assert children_hash.all?{|k,v| v.all?{|x| x.readonly?} }
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class SnapshotsConcernTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_relationships
|
12
|
+
instance = Post.new
|
13
|
+
|
14
|
+
assert instance.snapshots.empty?
|
15
|
+
assert instance.snapshot_items.empty?
|
16
|
+
|
17
|
+
assert_raises do
|
18
|
+
instance.snapshots << instance
|
19
|
+
end
|
20
|
+
|
21
|
+
instance.snapshots << ActiveSnapshot::Snapshot.new
|
22
|
+
|
23
|
+
assert_raises do
|
24
|
+
instance.snapshot_items << instance
|
25
|
+
end
|
26
|
+
|
27
|
+
instance.snapshot_items << ActiveSnapshot::SnapshotItem.new
|
28
|
+
|
29
|
+
assert_not instance.snapshots.empty?
|
30
|
+
assert_not instance.snapshot_items.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_create_snapshot!
|
34
|
+
@post = Post.first
|
35
|
+
|
36
|
+
#@user = User.first
|
37
|
+
|
38
|
+
snapshot = @post.create_snapshot!("foobar 1", user: @user, metadata: {foo: :bar})
|
39
|
+
assert_not snapshot.new_record?
|
40
|
+
|
41
|
+
snapshot = @post.create_snapshot!("foobar 2", user: @user)
|
42
|
+
assert_not snapshot.new_record?
|
43
|
+
|
44
|
+
snapshot = @post.create_snapshot!("foobar 3")
|
45
|
+
assert_not snapshot.new_record?
|
46
|
+
|
47
|
+
assert_raise do
|
48
|
+
@post.create_snapshot!("foobar 3")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_has_snapshot_children
|
53
|
+
klass = VolatilePost
|
54
|
+
|
55
|
+
assert_raise ArgumentError do
|
56
|
+
klass.has_snapshot_children
|
57
|
+
end
|
58
|
+
|
59
|
+
klass.has_snapshot_children do
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
|
63
|
+
assert klass.instance_variable_get(:@snapshot_children_proc).is_a?(Proc)
|
64
|
+
|
65
|
+
klass.new.children_to_snapshot
|
66
|
+
|
67
|
+
invalid = [
|
68
|
+
"foobar",
|
69
|
+
true,
|
70
|
+
false,
|
71
|
+
nil,
|
72
|
+
"",
|
73
|
+
[],
|
74
|
+
[:foobar, 123],
|
75
|
+
{foo: :bar},
|
76
|
+
{foo: {}},
|
77
|
+
{foo: {records: 'bar', delete_method: 'bar'}},
|
78
|
+
]
|
79
|
+
|
80
|
+
invalid.each do |x|
|
81
|
+
klass.has_snapshot_children do
|
82
|
+
x
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_raise ArgumentError do
|
86
|
+
klass.new.children_to_snapshot
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
valid = [
|
91
|
+
{},
|
92
|
+
{foo: []},
|
93
|
+
{foo: [:foobar, 123]},
|
94
|
+
{foo: {record: 'bar'}},
|
95
|
+
{foo: {records: 'bar'}},
|
96
|
+
{foo: {record: Post.limit(1) }},
|
97
|
+
{foo: {records: Post.limit(1) }},
|
98
|
+
{foo: {records: [], delete_method: ->(){} }},
|
99
|
+
{foo: {records: [], delete_method: proc{} }},
|
100
|
+
]
|
101
|
+
|
102
|
+
valid.each do |x|
|
103
|
+
klass.has_snapshot_children do
|
104
|
+
x
|
105
|
+
end
|
106
|
+
|
107
|
+
klass.new.children_to_snapshot
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
2
|
+
ENV["RAILS_ENV"] = "test"
|
3
|
+
|
4
|
+
require "active_snapshot"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'warning'
|
8
|
+
|
9
|
+
Warning.ignore(
|
10
|
+
%r{mail/parsers/address_lists_parser}, ### Hide mail gem warnings
|
11
|
+
)
|
12
|
+
rescue LoadError
|
13
|
+
# Do nothing
|
14
|
+
end
|
15
|
+
|
16
|
+
### Instantiates Rails
|
17
|
+
require File.expand_path("../dummy_app/config/environment.rb", __FILE__)
|
18
|
+
|
19
|
+
require "rails/test_help"
|
20
|
+
|
21
|
+
class ActiveSupport::TestCase
|
22
|
+
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
|
23
|
+
fixtures :all
|
24
|
+
end
|
25
|
+
|
26
|
+
Rails.backtrace_cleaner.remove_silencers!
|
27
|
+
|
28
|
+
require 'minitest/reporters'
|
29
|
+
Minitest::Reporters.use!(
|
30
|
+
Minitest::Reporters::DefaultReporter.new,
|
31
|
+
ENV,
|
32
|
+
Minitest.backtrace_filter
|
33
|
+
)
|
34
|
+
|
35
|
+
require "minitest/autorun"
|
36
|
+
|
37
|
+
# Run any available migration
|
38
|
+
if ActiveRecord.gem_version >= Gem::Version.new("6.0")
|
39
|
+
ActiveRecord::MigrationContext.new(File.expand_path("dummy_app/db/migrate/", __dir__), ActiveRecord::SchemaMigration).migrate
|
40
|
+
elsif ActiveRecord.gem_version >= Gem::Version.new("5.2")
|
41
|
+
ActiveRecord::MigrationContext.new(File.expand_path("dummy_app/db/migrate/", __dir__)).migrate
|
42
|
+
else
|
43
|
+
ActiveRecord::Migrator.migrate File.expand_path("dummy_app/db/migrate/", __dir__)
|
44
|
+
end
|
45
|
+
|
46
|
+
klasses = [
|
47
|
+
Post,
|
48
|
+
ActiveSnapshot::Snapshot,
|
49
|
+
ActiveSnapshot::SnapshotItem,
|
50
|
+
]
|
51
|
+
|
52
|
+
klasses.each do |klass|
|
53
|
+
if defined?(SQLite3)
|
54
|
+
ActiveRecord::Base.connection.execute("DELETE FROM #{klass.table_name};")
|
55
|
+
ActiveRecord::Base.connection.execute("UPDATE `sqlite_sequence` SET `seq` = 0 WHERE `name` = '#{klass.table_name}';")
|
56
|
+
else
|
57
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{klass.table_name}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
DATA = {}.with_indifferent_access
|
62
|
+
|
63
|
+
DATA[:shared_post] = Post.find_or_create_by!(a: 1, b: 3)
|
64
|
+
DATA[:shared_post].create_snapshot!('v1')
|
65
|
+
DATA[:shared_post].update_columns(a: 2, b: 4)
|
66
|
+
DATA[:shared_post].create_snapshot!('v2')
|
67
|
+
|
68
|
+
def assert_time_match(a, b)
|
69
|
+
format = "%d-%m-%Y %h:%M:%S.%L" ### MUST LIMIT THE MILLISECONDS TO 3 decimal places of accuracy, the rest are dropped
|
70
|
+
assert_equal a.strftime(format), b.strftime(format)
|
71
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class ActiveSnapshotTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
def test_exposes_main_module
|
6
|
+
assert ActiveSnapshot.is_a?(Module)
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_exposes_version
|
10
|
+
assert ActiveSnapshot::VERSION
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_snapshot_lifecycle
|
14
|
+
identifier = "snapshot-1"
|
15
|
+
|
16
|
+
klass = Post
|
17
|
+
|
18
|
+
parent = klass.first
|
19
|
+
|
20
|
+
original_parent_updated_at = parent.updated_at
|
21
|
+
|
22
|
+
child = parent.comments.create!(content: :foo)
|
23
|
+
original_child_updated_at = child.updated_at
|
24
|
+
|
25
|
+
orig_comments_size = parent.children_to_snapshot[:comments][:records].count
|
26
|
+
|
27
|
+
assert_difference ->{ ActiveSnapshot::Snapshot.count }, 1 do
|
28
|
+
assert_difference ->{ ActiveSnapshot::SnapshotItem.count }, (orig_comments_size+1) do
|
29
|
+
@snapshot = parent.create_snapshot!(identifier)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_equal (orig_comments_size+1), @snapshot.snapshot_items.size
|
34
|
+
|
35
|
+
parent.update_columns(updated_at: 1.day.from_now)
|
36
|
+
|
37
|
+
parent.update_columns(updated_at: 1.day.from_now)
|
38
|
+
|
39
|
+
child.destroy!
|
40
|
+
|
41
|
+
parent.comments.create!(content: :foo)
|
42
|
+
parent.comments.create!(content: :bar)
|
43
|
+
|
44
|
+
assert_no_difference ->{ ActiveSnapshot::Snapshot.count } do
|
45
|
+
assert_no_difference ->{ ActiveSnapshot::SnapshotItem.count } do
|
46
|
+
@snapshot.restore!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
assert_equal 1, ActiveSnapshot::Snapshot.where(identifier: identifier).count
|
51
|
+
|
52
|
+
parent.reload
|
53
|
+
|
54
|
+
assert_equal orig_comments_size, parent.children_to_snapshot[:comments][:records].count
|
55
|
+
|
56
|
+
### Test Data Chang
|
57
|
+
assert_time_match original_parent_updated_at, parent.updated_at
|
58
|
+
assert_time_match original_child_updated_at, parent.children_to_snapshot[:comments][:records].first.updated_at
|
59
|
+
end
|
60
|
+
end
|