rails_redshift_replicator 0.0.1
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/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/rails_redshift_replicator/application.js +13 -0
- data/app/assets/stylesheets/rails_redshift_replicator/application.css +15 -0
- data/app/controllers/rails_redshift_replicator/application_controller.rb +5 -0
- data/app/helpers/rails_redshift_replicator/application_helper.rb +4 -0
- data/app/models/rails_redshift_replicator/replication.rb +98 -0
- data/app/views/layouts/rails_redshift_replicator/application.html.erb +14 -0
- data/config/locales/rails_redshift_replicator.en.yml +20 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20160503214955_create_rails_redshift_replicator_replications.rb +24 -0
- data/db/migrate/20160509193335_create_table_rails_redshift_replicator_deleted_ids.rb +8 -0
- data/lib/generators/rails_redshift_replicator/install_generator.rb +25 -0
- data/lib/generators/templates/rails_redshift_replicator.rb +74 -0
- data/lib/rails_redshift_replicator.rb +229 -0
- data/lib/rails_redshift_replicator/adapters/generic.rb +40 -0
- data/lib/rails_redshift_replicator/adapters/mysql2.rb +22 -0
- data/lib/rails_redshift_replicator/adapters/postgresql.rb +37 -0
- data/lib/rails_redshift_replicator/adapters/sqlite.rb +27 -0
- data/lib/rails_redshift_replicator/deleter.rb +67 -0
- data/lib/rails_redshift_replicator/engine.rb +14 -0
- data/lib/rails_redshift_replicator/exporters/base.rb +215 -0
- data/lib/rails_redshift_replicator/exporters/full_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/exporters/identity_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/exporters/timed_replicator.rb +9 -0
- data/lib/rails_redshift_replicator/file_manager.rb +134 -0
- data/lib/rails_redshift_replicator/importers/base.rb +158 -0
- data/lib/rails_redshift_replicator/importers/full_replicator.rb +17 -0
- data/lib/rails_redshift_replicator/importers/identity_replicator.rb +15 -0
- data/lib/rails_redshift_replicator/importers/timed_replicator.rb +18 -0
- data/lib/rails_redshift_replicator/model/extension.rb +45 -0
- data/lib/rails_redshift_replicator/model/hair_trigger_extension.rb +8 -0
- data/lib/rails_redshift_replicator/replicable.rb +143 -0
- data/lib/rails_redshift_replicator/rlogger.rb +12 -0
- data/lib/rails_redshift_replicator/tools/analyze.rb +18 -0
- data/lib/rails_redshift_replicator/tools/vacuum.rb +77 -0
- data/lib/rails_redshift_replicator/version.rb +3 -0
- data/lib/tasks/rails_redshift_replicator_tasks.rake +4 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/post.rb +4 -0
- data/spec/dummy/app/models/tag.rb +4 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +26 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +37 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/rails_redshift_replicator.rb +59 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/locales/rails_redshift_replicator.en.yml +19 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20160504120421_create_test_tables.rb +40 -0
- data/spec/dummy/db/migrate/20160509225445_create_triggers_posts_delete_or_tags_delete_or_users_delete.rb +33 -0
- data/spec/dummy/db/migrate/20160511000937_create_rails_redshift_replicator_replications.rails_redshift_replicator.rb +25 -0
- data/spec/dummy/db/migrate/20160511000938_create_table_rails_redshift_replicator_deleted_ids.rails_redshift_replicator.rb +9 -0
- data/spec/dummy/db/schema.rb +99 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +1623 -0
- data/spec/dummy/log/test.log +95379 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/rails_redshift_replicator_development +0 -0
- data/spec/factories/rails_redshift_replicator_replications.rb +31 -0
- data/spec/integration/rails_redshift_replicator_spec.rb +148 -0
- data/spec/integration/setup_spec.rb +149 -0
- data/spec/lib/rails_redshift_replicator/deleter_spec.rb +90 -0
- data/spec/lib/rails_redshift_replicator/exporters/base_spec.rb +326 -0
- data/spec/lib/rails_redshift_replicator/exporters/full_replicator_spec.rb +33 -0
- data/spec/lib/rails_redshift_replicator/exporters/identity_replicator_spec.rb +40 -0
- data/spec/lib/rails_redshift_replicator/exporters/timed_replicator_spec.rb +43 -0
- data/spec/lib/rails_redshift_replicator/file_manager_spec.rb +90 -0
- data/spec/lib/rails_redshift_replicator/importers/base_spec.rb +102 -0
- data/spec/lib/rails_redshift_replicator/importers/full_replicator_spec.rb +27 -0
- data/spec/lib/rails_redshift_replicator/importers/identity_replicator_spec.rb +26 -0
- data/spec/lib/rails_redshift_replicator/importers/timed_replicator_spec.rb +26 -0
- data/spec/lib/rails_redshift_replicator/model/extension_spec.rb +36 -0
- data/spec/lib/rails_redshift_replicator/replicable_spec.rb +230 -0
- data/spec/lib/rails_redshift_replicator/rlogger_spec.rb +22 -0
- data/spec/lib/rails_redshift_replicator/tools/analyze_spec.rb +15 -0
- data/spec/lib/rails_redshift_replicator/tools/vacuum_spec.rb +65 -0
- data/spec/lib/rails_redshift_replicator_spec.rb +110 -0
- data/spec/models/rails_redshift_replicator/replication_spec.rb +104 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/csv/invalid_user.csv +12 -0
- data/spec/support/csv/valid_post.csv +2 -0
- data/spec/support/csv/valid_tags_users.csv +1 -0
- data/spec/support/csv/valid_user.csv +2 -0
- data/spec/support/rails_redshift_replicator_helpers.rb +95 -0
- metadata +430 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Deleter do
|
|
4
|
+
let(:replicable) { RailsRedshiftReplicator.replicables[:users] }
|
|
5
|
+
let(:deleter) { RailsRedshiftReplicator::Deleter.new(replicable) }
|
|
6
|
+
describe '#purge_deleted' do
|
|
7
|
+
before { 2.times { User.create } }
|
|
8
|
+
it 'purge deleted records for the given table' do
|
|
9
|
+
expect(deleter.deleted_ids.count).to eq 0
|
|
10
|
+
User.first.destroy
|
|
11
|
+
expect(deleter.deleted_ids.count).to eq 1
|
|
12
|
+
deleter.purge_deleted
|
|
13
|
+
expect(deleter.deleted_ids.count).to eq 0
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
describe '#delete_on_target' do
|
|
17
|
+
it 'calls redshift to delete' do
|
|
18
|
+
expect(RailsRedshiftReplicator.connection).to receive(:exec).and_return(double("result", result_status: 1))
|
|
19
|
+
deleter.delete_on_target
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
describe '#handle_delete_propagation' do
|
|
23
|
+
context 'when tracking deleted' do
|
|
24
|
+
context 'and has deleted records' do
|
|
25
|
+
before do
|
|
26
|
+
allow(RailsRedshiftReplicator.connection).to receive(:exec)
|
|
27
|
+
2.times { User.create }
|
|
28
|
+
User.delete_all
|
|
29
|
+
end
|
|
30
|
+
it 'calls #delete_on_target' do
|
|
31
|
+
expect(deleter).to receive(:delete_on_target)
|
|
32
|
+
deleter.handle_delete_propagation
|
|
33
|
+
end
|
|
34
|
+
context 'if delete on target is success' do
|
|
35
|
+
before { allow(RailsRedshiftReplicator.connection).to receive(:exec).and_return(double("result", result_status: 1)) }
|
|
36
|
+
it 'calls #purge_deleted' do
|
|
37
|
+
expect(deleter).to receive(:purge_deleted)
|
|
38
|
+
deleter.handle_delete_propagation
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
context 'if delete on target fails' do
|
|
42
|
+
before { allow(RailsRedshiftReplicator.connection).to receive(:exec).and_return(double("result", result_status: 0)) }
|
|
43
|
+
it 'does not call calls #purge_deleted and logs error' do
|
|
44
|
+
expect(deleter).not_to receive(:purge_deleted)
|
|
45
|
+
expect(RailsRedshiftReplicator.logger).to receive(:error)
|
|
46
|
+
deleter.handle_delete_propagation
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
context 'and without deleted records' do
|
|
51
|
+
it 'does not call #delete_on_target' do
|
|
52
|
+
expect(deleter).not_to receive(:delete_on_target)
|
|
53
|
+
deleter.handle_delete_propagation
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
context 'if not tracking deleted' do
|
|
58
|
+
before { replicable.instance_variable_set("@tracking", false) }
|
|
59
|
+
it 'does not call #delete_on_target' do
|
|
60
|
+
expect(deleter).not_to receive(:delete_on_target)
|
|
61
|
+
deleter.handle_delete_propagation
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
describe '#deleted_ids' do
|
|
66
|
+
it 'returns array of deleted ids' do
|
|
67
|
+
user = create :user
|
|
68
|
+
id = user.id
|
|
69
|
+
User.delete_all
|
|
70
|
+
expect(deleter.deleted_ids).to eq [id.to_s]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
describe '#has_deleted_ids?' do
|
|
74
|
+
context 'when there are deleted records' do
|
|
75
|
+
before do
|
|
76
|
+
2.times { User.create }
|
|
77
|
+
User.delete_all
|
|
78
|
+
end
|
|
79
|
+
it 'returns true' do
|
|
80
|
+
expect(deleter.has_deleted_ids?).to be_truthy
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
context "when there aren't deleted records" do
|
|
84
|
+
it 'returns false' do
|
|
85
|
+
expect(deleter.has_deleted_ids?).to be_falsy
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'active_support/core_ext/time/calculations'
|
|
3
|
+
|
|
4
|
+
describe RailsRedshiftReplicator::Exporters::Base do
|
|
5
|
+
let(:user_replicable) { RailsRedshiftReplicator::Replicable.new(:identity_replicator, source_table: :users)}
|
|
6
|
+
let(:post_replicable) { RailsRedshiftReplicator::Replicable.new(:timed_replicator, source_table: :posts)}
|
|
7
|
+
let(:full_replicable) { RailsRedshiftReplicator::Replicable.new(:full_replicator, source_table: :tags_users)}
|
|
8
|
+
let(:user_exporter) { RailsRedshiftReplicator::Exporters::IdentityReplicator.new(user_replicable) }
|
|
9
|
+
let(:post_exporter) { RailsRedshiftReplicator::Exporters::TimedReplicator.new(post_replicable) }
|
|
10
|
+
let(:habtm_exporter) { RailsRedshiftReplicator::Exporters::FullReplicator.new(full_replicable) }
|
|
11
|
+
before { RailsRedshiftReplicator.debug_mode = true }
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe 'Instance Methods' do
|
|
16
|
+
describe "#table_indexes" do
|
|
17
|
+
context "with User model" do
|
|
18
|
+
it "returns user's table indexes" do
|
|
19
|
+
expect(user_exporter.table_indexes).to contain_exactly("id", "login", "age")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#upload" do
|
|
25
|
+
context "with csv format" do
|
|
26
|
+
let(:files) { %w(file1 file2) }
|
|
27
|
+
before { user_exporter.replication = create(:redshift_replication, source_table: "users", export_format: "csv") }
|
|
28
|
+
before { user_exporter.file_names = files }
|
|
29
|
+
context 'when exporter is in an error state' do
|
|
30
|
+
before { user_exporter.errors = "some error"}
|
|
31
|
+
it 'returns nil' do
|
|
32
|
+
expect(user_exporter.upload).to be_nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
context 'when exporter is valid', focus: true do
|
|
36
|
+
it "calls csv uploader" do
|
|
37
|
+
expect(user_exporter.file_manager).to receive(:upload_csv).with(files)
|
|
38
|
+
user_exporter.upload
|
|
39
|
+
end
|
|
40
|
+
it "changes replication state to uploaded" do
|
|
41
|
+
expect(user_exporter.file_manager).to receive(:upload_csv).with(files)
|
|
42
|
+
user_exporter.upload
|
|
43
|
+
expect(user_exporter.replication.state).to eq "uploaded"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe '#connection_adapter' do
|
|
50
|
+
context 'when Mysql2' do
|
|
51
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return("Mysql2") }
|
|
52
|
+
it 'returns RailsRedshiftReplicator::Adapters::Mysql2' do
|
|
53
|
+
expect(user_exporter.connection_adapter).to be_an RailsRedshiftReplicator::Adapters::Mysql2
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
context 'when PostgreSQL' do
|
|
57
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return("PostgreSQL") }
|
|
58
|
+
it 'returns RailsRedshiftReplicator::Adapters::Postgresql' do
|
|
59
|
+
expect(user_exporter.connection_adapter).to be_an RailsRedshiftReplicator::Adapters::PostgreSQL
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
context 'when SQLite' do
|
|
63
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return("SQLite") }
|
|
64
|
+
it 'returns RailsRedshiftReplicator::Adapters::Sqlite' do
|
|
65
|
+
expect(user_exporter.connection_adapter).to be_an RailsRedshiftReplicator::Adapters::SQLite
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "#has_index?" do
|
|
71
|
+
before { allow(user_exporter).to receive(:table_indexes).and_return [:id, :updated_at] }
|
|
72
|
+
context "when replication_field has an index" do
|
|
73
|
+
before { allow(user_exporter).to receive(:replication_field).and_return(:updated_at) }
|
|
74
|
+
it "returns true" do
|
|
75
|
+
expect(user_exporter).to be_has_index
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
context "when replication_field doesn't have an index" do
|
|
79
|
+
before { allow(user_exporter).to receive(:replication_field).and_return(:login) }
|
|
80
|
+
it "returns false" do
|
|
81
|
+
expect(user_exporter).not_to be_has_index
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
describe "#replication_field" do
|
|
88
|
+
it "returns the replicable replication_field" do
|
|
89
|
+
expect(RailsRedshiftReplicator::Exporters::Base.new(user_replicable).replication_field).to eq 'id'
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe "#check_target_table" do
|
|
94
|
+
context "when the target table exists on redshift" do
|
|
95
|
+
before { recreate_users_table }
|
|
96
|
+
it "doesn't set an error" do
|
|
97
|
+
user_exporter.check_target_table
|
|
98
|
+
expect(user_exporter.errors).to be_nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
context "when the target table doesn't exist on redshift" do
|
|
102
|
+
before(:all) { drop_redshift_table(:users) }
|
|
103
|
+
after(:all) { recreate_users_table }
|
|
104
|
+
it "sets the exporter to an error state" do
|
|
105
|
+
user_exporter.check_target_table
|
|
106
|
+
expect(user_exporter.errors).to be_present
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe '#query_command' do
|
|
112
|
+
before { allow(user_exporter).to receive(:fields_to_sync).and_return(%w(id user_id publication_id)) }
|
|
113
|
+
before { allow(user_exporter).to receive(:last_record).and_return(10) }
|
|
114
|
+
|
|
115
|
+
let(:sql) { "SELECT id,user_id,publication_id FROM users WHERE 1=1 AND id > '5' AND id <= '10' OR id IS NULL" }
|
|
116
|
+
context 'when adapter is Mysql2' do
|
|
117
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return('Mysql2') }
|
|
118
|
+
it "executes query with streaming option" do
|
|
119
|
+
expect(user_exporter.ar_client.instance_variable_get("@connection")).to receive(:query).with(sql, stream: true)
|
|
120
|
+
user_exporter.records(5)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
context "when adapter is SQLite", focus: true do
|
|
124
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return('SQLite') }
|
|
125
|
+
it "executes query without other arguments" do
|
|
126
|
+
expect(user_exporter.ar_client).to receive(:exec_query).with(sql)
|
|
127
|
+
user_exporter.records(5)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
context "when adapter is PostgreSQL" do
|
|
131
|
+
before { allow(user_exporter.ar_client).to receive(:adapter_name).and_return('PostgreSQL') }
|
|
132
|
+
xit "executes query using single row mode" do
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe '#records' do
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
describe "#build_query_sql" do
|
|
142
|
+
before { allow(user_exporter).to receive(:fields_to_sync).and_return(%w(id user_id publication_id)) }
|
|
143
|
+
context "when there's a last record" do
|
|
144
|
+
before { allow(user_exporter).to receive(:last_record).and_return(10) }
|
|
145
|
+
context "when an initial record is given" do
|
|
146
|
+
let(:sql) { "SELECT id,user_id,publication_id FROM users WHERE 1=1 AND id > '5' AND id <= '10' OR id IS NULL" }
|
|
147
|
+
it "builds correct sql" do
|
|
148
|
+
expect(user_exporter.build_query_sql(5)).to eq sql
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
context "when an initial record isn't given" do
|
|
152
|
+
let(:sql) { "SELECT id,user_id,publication_id FROM users WHERE 1=1 AND id <= '10' OR id IS NULL" }
|
|
153
|
+
it "builds correct sql" do
|
|
154
|
+
expect(user_exporter.build_query_sql).to eq sql
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
context "when a last_record can't be found" do
|
|
159
|
+
before { allow(user_exporter).to receive(:last_record).and_return(nil) }
|
|
160
|
+
context "when an initial record is given" do
|
|
161
|
+
let(:sql) { "SELECT id,user_id,publication_id FROM users WHERE 1=1 AND id > '5'" }
|
|
162
|
+
it "builds correct sql" do
|
|
163
|
+
expect(user_exporter.build_query_sql(5)).to eq sql
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
context "when an initial record isn't given" do
|
|
167
|
+
let(:sql) { "SELECT id,user_id,publication_id FROM users WHERE 1=1" }
|
|
168
|
+
it "builds correct sql" do
|
|
169
|
+
expect(user_exporter.build_query_sql).to eq sql
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
describe "#from_record" do
|
|
176
|
+
context "when there's a previous replication record" do
|
|
177
|
+
let!(:replication) { create :redshift_replication, target_table: 'users', last_record: 1 }
|
|
178
|
+
let!(:replication2) { create :redshift_replication, target_table: 'users', last_record: 5 }
|
|
179
|
+
it "returns the id or timestamp of the last_record on the most recent complete replication" do
|
|
180
|
+
expect(user_exporter.from_record).to eq '5'
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
context "when there isn't a previous replication record" do
|
|
184
|
+
it "returns nil" do
|
|
185
|
+
expect(user_exporter.from_record).to be_nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
describe "#initialize_replication" do
|
|
191
|
+
before do
|
|
192
|
+
allow(user_exporter).to receive(:last_record).and_return(3)
|
|
193
|
+
allow(user_exporter).to receive(:from_record).and_return(1)
|
|
194
|
+
user_exporter.initialize_replication("replication_file", "csv", 2)
|
|
195
|
+
end
|
|
196
|
+
let(:replication) { user_exporter.replication }
|
|
197
|
+
|
|
198
|
+
it "creates a replication record" do
|
|
199
|
+
expect(replication.export_format).to eq "csv"
|
|
200
|
+
expect(replication.slices).to eq 2
|
|
201
|
+
expect(replication.record_count).to be_nil
|
|
202
|
+
expect(replication.key).to eq "rrr/users/replication_file"
|
|
203
|
+
expect(replication.last_record).to eq '3'
|
|
204
|
+
expect(replication.first_record).to eq '1'
|
|
205
|
+
expect(replication.state).to eq "exporting"
|
|
206
|
+
expect(replication.replication_type).to eq "identity_replicator"
|
|
207
|
+
expect(replication.target_table).to eq "users"
|
|
208
|
+
expect(replication.source_table).to eq "users"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
describe "#export" do
|
|
213
|
+
before { allow(user_exporter).to receive(:fields_to_sync).and_return(%w(id login age)) }
|
|
214
|
+
context "when there are incomplete replications" do
|
|
215
|
+
before { create :redshift_replication, source_table: "users", state: "uploaded" }
|
|
216
|
+
it "doesn't create replication record" do
|
|
217
|
+
user_exporter.export
|
|
218
|
+
expect(user_exporter.replication).to be_nil
|
|
219
|
+
end
|
|
220
|
+
it "returns nil" do
|
|
221
|
+
expect(user_exporter.export).to be_nil
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
context "when there are no incomplete replications" do
|
|
225
|
+
before { create :redshift_replication, source_table: "users", state: "imported" }
|
|
226
|
+
context "and there are records to export" do
|
|
227
|
+
before { create :user }
|
|
228
|
+
context 'and exporter is in error state' do
|
|
229
|
+
before { user_exporter.errors = 'some error' }
|
|
230
|
+
it 'returns false' do
|
|
231
|
+
expect(user_exporter.export).to be_nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
context 'and exporter is valid' do
|
|
235
|
+
it "creates export files" do
|
|
236
|
+
file_paths = user_exporter.export
|
|
237
|
+
expect(file_paths).to be_an_instance_of Array
|
|
238
|
+
expect(file_paths).to be_present
|
|
239
|
+
end
|
|
240
|
+
it "flags replication as exported" do
|
|
241
|
+
user_exporter.export
|
|
242
|
+
expect(user_exporter.replication.state).to eq "exported"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
context "and there aren't any records to export" do
|
|
247
|
+
it "doesn't create replication record" do
|
|
248
|
+
user_exporter.export
|
|
249
|
+
expect(user_exporter.replication).to be_nil
|
|
250
|
+
end
|
|
251
|
+
it "retorns nil" do
|
|
252
|
+
expect(user_exporter.export).to be_nil
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe "#last_replication" do
|
|
259
|
+
let!(:replication1) { create :redshift_replication, source_table: "users" }
|
|
260
|
+
let!(:replication2) { create :redshift_replication, source_table: "users" }
|
|
261
|
+
let!(:replication3) { create :redshift_replication, source_table: "posts" }
|
|
262
|
+
it "retuns the last replication record for a given table" do
|
|
263
|
+
expect(user_exporter.last_replication).to eq replication2
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
describe "#export_and_upload" do
|
|
268
|
+
context "when the target table doesn't exist on redshift" do
|
|
269
|
+
before(:all) { drop_redshift_table(:users) }
|
|
270
|
+
after(:all) { recreate_users_table }
|
|
271
|
+
it "doesn't call #export" do
|
|
272
|
+
expect(user_exporter.export_and_upload).not_to receive(:export)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
context "when the target table exists on redshift" do
|
|
276
|
+
before(:all) { recreate_users_table }
|
|
277
|
+
it "calls #export" do
|
|
278
|
+
expect(user_exporter).to receive(:export)
|
|
279
|
+
user_exporter.export_and_upload
|
|
280
|
+
end
|
|
281
|
+
context "when there are records to export" do
|
|
282
|
+
before { allow(user_exporter).to receive(:export).and_return("file") }
|
|
283
|
+
it "calls #upload" do
|
|
284
|
+
expect(user_exporter).to receive(:upload)
|
|
285
|
+
user_exporter.export_and_upload
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
context "when there are no records to export" do
|
|
289
|
+
it "doesn't call #upload" do
|
|
290
|
+
expect(user_exporter.export_and_upload).not_to receive(:upload)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe "#last_record" do
|
|
298
|
+
let!(:user1) { create :user }
|
|
299
|
+
let!(:post1) { create :post }
|
|
300
|
+
context "when the last replication record uses time based replication" do
|
|
301
|
+
it "returns a date" do
|
|
302
|
+
Timecop.freeze Date.tomorrow do
|
|
303
|
+
post2 = create :post
|
|
304
|
+
formatted = Time.parse(post_exporter.last_record).to_s(:db)
|
|
305
|
+
expect(formatted).to eq post2.updated_at.to_s(:db)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
context "when the last replication record uses an identity column" do
|
|
310
|
+
let!(:post) { create :post }
|
|
311
|
+
it "returns the id" do
|
|
312
|
+
user1 = create :user
|
|
313
|
+
user2 = create :user
|
|
314
|
+
expect(user_exporter.last_record.to_s).to eq user2.id.to_s
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
describe "fields_to_sync" do
|
|
320
|
+
before { recreate_users_table }
|
|
321
|
+
it "returns list of columns on the target table on redshift" do
|
|
322
|
+
expect(user_exporter.fields_to_sync).to contain_exactly("age", "confirmed", "created_at", "id", "login", "updated_at")
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RailsRedshiftReplicator::Exporters::FullReplicator do
|
|
4
|
+
let(:file_manager) { RailsRedshiftReplicator::FileManager.new}
|
|
5
|
+
let(:replicable) { RailsRedshiftReplicator::Replicable.new(:full_replicator, source_table: :tags_users) }
|
|
6
|
+
let(:s3_bucket) { Aws::S3::Bucket.new(name: RailsRedshiftReplicator.s3_bucket_params[:bucket], client: file_manager.s3_client) }
|
|
7
|
+
describe "#replication_field", :focus do
|
|
8
|
+
it "returns the replicable replication_field" do
|
|
9
|
+
expect(RailsRedshiftReplicator::Exporters::FullReplicator.new(replicable).replication_field).to be_nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
describe 'Integration Test' do
|
|
13
|
+
before(:all) { recreate_tags_users_table }
|
|
14
|
+
it "exports full replicator type replication" do
|
|
15
|
+
model = :tags_users
|
|
16
|
+
# first export
|
|
17
|
+
tag = create :tag
|
|
18
|
+
user = create :user
|
|
19
|
+
tag.users << user
|
|
20
|
+
RailsRedshiftReplicator::Exporters::FullReplicator.new(replicable).export_and_upload
|
|
21
|
+
replication1 = RailsRedshiftReplicator::Replication.from_table("tags_users").last
|
|
22
|
+
expect(replication1.state).to eq "uploaded"
|
|
23
|
+
expect(replication1.record_count).to eq 1
|
|
24
|
+
file_body = s3_bucket.object("#{replication1.key}.aa").get.body.read
|
|
25
|
+
expect(file_body).to match(/#{user.id},#{tag.id}/)
|
|
26
|
+
replication1.imported!
|
|
27
|
+
# export without new records
|
|
28
|
+
RailsRedshiftReplicator::Exporters::FullReplicator.new(replicable).export_and_upload
|
|
29
|
+
replication2 = RailsRedshiftReplicator::Replication.from_table("tags_users").last
|
|
30
|
+
expect(replication2.record_count).to eq 1
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|