cranium 0.2.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/.gitignore +21 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +3 -0
- data/Vagrantfile +24 -0
- data/bin/cranium +9 -0
- data/config/cucumber.yml +9 -0
- data/cranium.gemspec +26 -0
- data/db/setup.sql +8 -0
- data/docker-compose.yml +8 -0
- data/examples/config.rb +14 -0
- data/examples/deduplication.rb +27 -0
- data/examples/import_csv_with_field_lookup_inserting_new_dimension_keys.rb +26 -0
- data/examples/incremental_extract.rb +17 -0
- data/examples/lookup_with_multiple_fields.rb +25 -0
- data/features/archive.feature +49 -0
- data/features/extract/incremental_extract.feature +56 -0
- data/features/extract/simple_extract.feature +85 -0
- data/features/import/import_csv_to_database_as_delta.feature +38 -0
- data/features/import/import_csv_to_database_with_delete_insert_merging.feature +51 -0
- data/features/import/import_csv_to_database_with_truncate_insert.feature +49 -0
- data/features/import/import_csv_to_database_with_update_merging.feature +46 -0
- data/features/import/import_csv_with_always_inserting_new_dimension_keys.feature +137 -0
- data/features/import/import_csv_with_field_lookup_inserting_new_dimension_keys.feature +62 -0
- data/features/import/import_csv_with_field_lookup_transformation.feature +125 -0
- data/features/import/import_csv_with_transformation.feature +55 -0
- data/features/import/import_multiple_csv_files_without_transformations.feature +44 -0
- data/features/import/import_with_load_id_from_sequence.feature +53 -0
- data/features/import/import_with_lookup_from_multiple_fields.feature +64 -0
- data/features/read.feature +56 -0
- data/features/remove.feature +44 -0
- data/features/restore_database_connection.feature +55 -0
- data/features/step_definitions/database_table_steps.rb +40 -0
- data/features/step_definitions/definition_steps.rb +3 -0
- data/features/step_definitions/execution_steps.rb +23 -0
- data/features/step_definitions/file_steps.rb +39 -0
- data/features/support/class_extensions.rb +24 -0
- data/features/support/env.rb +27 -0
- data/features/support/randomize.rb +22 -0
- data/features/support/stop_on_first_error.rb +5 -0
- data/features/transform/deduplication.feature +37 -0
- data/features/transform/empty_transformation.feature +72 -0
- data/features/transform/join.feature +180 -0
- data/features/transform/join_multiple_files_into_one_output_file.feature +46 -0
- data/features/transform/output_rows.feature +70 -0
- data/features/transform/projection.feature +34 -0
- data/features/transform/raw_ruby_transformation.feature +69 -0
- data/features/transform/split_field.feature +39 -0
- data/lib/cranium/application.rb +104 -0
- data/lib/cranium/archiver.rb +36 -0
- data/lib/cranium/attribute_dsl.rb +43 -0
- data/lib/cranium/command_line_options.rb +27 -0
- data/lib/cranium/configuration.rb +33 -0
- data/lib/cranium/data_importer.rb +35 -0
- data/lib/cranium/data_reader.rb +48 -0
- data/lib/cranium/data_transformer.rb +126 -0
- data/lib/cranium/database.rb +36 -0
- data/lib/cranium/definition_registry.rb +21 -0
- data/lib/cranium/dimension_manager.rb +65 -0
- data/lib/cranium/dsl/database_definition.rb +23 -0
- data/lib/cranium/dsl/extract_definition.rb +28 -0
- data/lib/cranium/dsl/import_definition.rb +50 -0
- data/lib/cranium/dsl/source_definition.rb +67 -0
- data/lib/cranium/dsl.rb +100 -0
- data/lib/cranium/extensions/file.rb +7 -0
- data/lib/cranium/extensions/sequel_greenplum.rb +30 -0
- data/lib/cranium/external_table.rb +75 -0
- data/lib/cranium/extract/data_extractor.rb +11 -0
- data/lib/cranium/extract/storage.rb +57 -0
- data/lib/cranium/extract/strategy/base.rb +27 -0
- data/lib/cranium/extract/strategy/incremental.rb +16 -0
- data/lib/cranium/extract/strategy/simple.rb +9 -0
- data/lib/cranium/extract/strategy.rb +7 -0
- data/lib/cranium/extract.rb +7 -0
- data/lib/cranium/import_strategy/base.rb +55 -0
- data/lib/cranium/import_strategy/delete_insert.rb +40 -0
- data/lib/cranium/import_strategy/delta.rb +8 -0
- data/lib/cranium/import_strategy/merge.rb +50 -0
- data/lib/cranium/import_strategy/truncate_insert.rb +19 -0
- data/lib/cranium/import_strategy.rb +9 -0
- data/lib/cranium/logging.rb +15 -0
- data/lib/cranium/profiling.rb +13 -0
- data/lib/cranium/progress_output.rb +37 -0
- data/lib/cranium/sequel/hash.rb +32 -0
- data/lib/cranium/sequel.rb +5 -0
- data/lib/cranium/source_registry.rb +21 -0
- data/lib/cranium/test_framework/cucumber_table.rb +140 -0
- data/lib/cranium/test_framework/database_entity.rb +29 -0
- data/lib/cranium/test_framework/database_sequence.rb +16 -0
- data/lib/cranium/test_framework/database_table.rb +33 -0
- data/lib/cranium/test_framework/upload_directory.rb +39 -0
- data/lib/cranium/test_framework/world.rb +66 -0
- data/lib/cranium/test_framework.rb +10 -0
- data/lib/cranium/transformation/duplication_index.rb +42 -0
- data/lib/cranium/transformation/index.rb +83 -0
- data/lib/cranium/transformation/join.rb +141 -0
- data/lib/cranium/transformation/sequence.rb +42 -0
- data/lib/cranium/transformation.rb +8 -0
- data/lib/cranium/transformation_record.rb +45 -0
- data/lib/cranium.rb +57 -0
- data/rake/test.rake +31 -0
- data/spec/cranium/application_spec.rb +166 -0
- data/spec/cranium/archiver_spec.rb +44 -0
- data/spec/cranium/command_line_options_spec.rb +32 -0
- data/spec/cranium/configuration_spec.rb +31 -0
- data/spec/cranium/data_importer_spec.rb +55 -0
- data/spec/cranium/data_transformer_spec.rb +16 -0
- data/spec/cranium/database_spec.rb +69 -0
- data/spec/cranium/definition_registry_spec.rb +45 -0
- data/spec/cranium/dimension_manager_spec.rb +63 -0
- data/spec/cranium/dsl/database_definition_spec.rb +23 -0
- data/spec/cranium/dsl/extract_definition_spec.rb +76 -0
- data/spec/cranium/dsl/import_definition_spec.rb +153 -0
- data/spec/cranium/dsl/source_definition_spec.rb +84 -0
- data/spec/cranium/dsl_spec.rb +119 -0
- data/spec/cranium/external_table_spec.rb +71 -0
- data/spec/cranium/extract/storage_spec.rb +125 -0
- data/spec/cranium/logging_spec.rb +37 -0
- data/spec/cranium/sequel/hash_spec.rb +56 -0
- data/spec/cranium/source_registry_spec.rb +31 -0
- data/spec/cranium/test_framework/cucumber_table_spec.rb +144 -0
- data/spec/cranium/transformation/duplication_index_spec.rb +75 -0
- data/spec/cranium/transformation/index_spec.rb +178 -0
- data/spec/cranium/transformation/join_spec.rb +43 -0
- data/spec/cranium/transformation/sequence_spec.rb +83 -0
- data/spec/cranium/transformation_record_spec.rb +78 -0
- data/spec/cranium_spec.rb +53 -0
- data/spec/spec_helper.rb +1 -0
- metadata +362 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
require 'sequel'
|
|
3
|
+
require 'sequel/adapters/mock'
|
|
4
|
+
|
|
5
|
+
describe Cranium::Database do
|
|
6
|
+
|
|
7
|
+
let(:database) { Cranium::Database }
|
|
8
|
+
let(:connection) { Sequel::Mock::Database.new }
|
|
9
|
+
let(:other_connection) { Sequel::Mock::Database.new }
|
|
10
|
+
|
|
11
|
+
before(:each) do
|
|
12
|
+
allow(Cranium).to receive(:configuration).and_return(Cranium::Configuration.new.tap do |config|
|
|
13
|
+
config.greenplum_connection_string = "connection string"
|
|
14
|
+
config.loggers = "loggers"
|
|
15
|
+
end)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
describe ".connection" do
|
|
20
|
+
before { Cranium::Database.instance_variable_set :@connection, nil }
|
|
21
|
+
|
|
22
|
+
it "should connect to the DB" do
|
|
23
|
+
expect(Sequel).to receive(:connect).with("connection string", :loggers => "loggers").and_return connection
|
|
24
|
+
|
|
25
|
+
expect(database.connection).to eq connection
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "should return the same object every time" do
|
|
29
|
+
allow(Sequel).to receive(:connect).and_return(connection, other_connection)
|
|
30
|
+
|
|
31
|
+
expect(database.connection).to eq database.connection
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
describe ".[]" do
|
|
37
|
+
before do
|
|
38
|
+
database.instance_variable_set :@connections, nil
|
|
39
|
+
database.instance_variable_set :@definitions, nil
|
|
40
|
+
|
|
41
|
+
database.register_database :dwh do
|
|
42
|
+
connect_to "other connection string"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "should return the specified database connection" do
|
|
47
|
+
expect(Sequel).to receive(:connect).with("other connection string", :loggers => "loggers").and_return connection
|
|
48
|
+
|
|
49
|
+
expect(database[:dwh]).to eq connection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "should memoize the result of a previous call" do
|
|
53
|
+
allow(Sequel).to receive(:connect).and_return(connection, other_connection)
|
|
54
|
+
|
|
55
|
+
expect(database[:dwh]).to eq database[:dwh]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should memoize connections by name" do
|
|
59
|
+
database.register_database :dwh2 do
|
|
60
|
+
connect_to "other connection string 2"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
allow(Sequel).to receive(:connect).and_return(connection, other_connection)
|
|
64
|
+
|
|
65
|
+
expect(database[:dwh]).not_to eq database[:dwh2]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DefinitionRegistry do
|
|
4
|
+
|
|
5
|
+
let(:registry) { Cranium::DefinitionRegistry.new Cranium::DSL::DatabaseDefinition }
|
|
6
|
+
|
|
7
|
+
describe "#[]" do
|
|
8
|
+
it "should return nil if a definition with the specified name wasn't registered yet" do
|
|
9
|
+
expect(registry[:name]).to be_nil
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
describe "#register_definition" do
|
|
15
|
+
it "should return the newly registered source" do
|
|
16
|
+
expect(registry.register_definition(:test_database) {}).to be_a Cranium::DSL::DatabaseDefinition
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should register a new database definition and configure it through the block passed" do
|
|
20
|
+
expected_definition = Cranium::DSL::DatabaseDefinition.new :test_database
|
|
21
|
+
expected_definition.connect_to "connection string"
|
|
22
|
+
|
|
23
|
+
registry.register_definition :test_database do
|
|
24
|
+
connect_to "connection string"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
expect(registry[:test_database]).to eq expected_definition
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
it "should register a new source definition and configure it through the block passed" do
|
|
32
|
+
registry = Cranium::DefinitionRegistry.new Cranium::DSL::SourceDefinition
|
|
33
|
+
expected_definition = Cranium::DSL::SourceDefinition.new :test_source
|
|
34
|
+
expected_definition.file "test.csv"
|
|
35
|
+
|
|
36
|
+
registry.register_definition :test_source do
|
|
37
|
+
file "test.csv"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
expect(registry[:test_source]).to eq expected_definition
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DimensionManager do
|
|
4
|
+
describe "#insert" do
|
|
5
|
+
|
|
6
|
+
context "with single key" do
|
|
7
|
+
let(:manager) { Cranium::DimensionManager.new :table, :source_key }
|
|
8
|
+
|
|
9
|
+
it "should store a new record for insertion" do
|
|
10
|
+
manager.insert :target_key, target_key: 123, name: "John"
|
|
11
|
+
|
|
12
|
+
expect(manager.rows).to eq [{target_key: 123, name: "John"}]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "should raise an error if the key field's value isn't specified in the record" do
|
|
16
|
+
expect { manager.insert :target_key, name: "John" }.to raise_error ArgumentError, "Required attribute 'target_key' missing"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should return the key field's value in an array" do
|
|
20
|
+
expect(manager.insert(:target_key, target_key: 123, name: "John")).to eq 123
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "if one of the values in the record is a sequence" do
|
|
24
|
+
it "should insert the next value of the specified sequence" do
|
|
25
|
+
sequence = Cranium::Transformation::Sequence.new :id_seq
|
|
26
|
+
allow(sequence).to receive(:next_value).and_return(123)
|
|
27
|
+
|
|
28
|
+
manager.insert :target_key, target_key: sequence
|
|
29
|
+
|
|
30
|
+
expect(manager.rows).to eq [{target_key: 123}]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#create_cache_for_field" do
|
|
37
|
+
context "with single key" do
|
|
38
|
+
let(:manager) { Cranium::DimensionManager.new :table, [:key] }
|
|
39
|
+
|
|
40
|
+
it "should load key-value pairs into a hash" do
|
|
41
|
+
database = double("Database connection")
|
|
42
|
+
allow(manager).to receive(:db).and_return database
|
|
43
|
+
|
|
44
|
+
expect(database).to receive(:select_map).with([:key, :value_field]).and_return([[1, 10], [2, 20]])
|
|
45
|
+
|
|
46
|
+
expect(manager.create_cache_for_field(:value_field)).to eq [1] => 10, [2] => 20
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "with multiple keys" do
|
|
51
|
+
let(:manager) { Cranium::DimensionManager.new :table, [:key_1, :key_2] }
|
|
52
|
+
|
|
53
|
+
it "should load key-value pairs into a hash" do
|
|
54
|
+
database = double("Database connection")
|
|
55
|
+
allow(manager).to receive(:db).and_return database
|
|
56
|
+
|
|
57
|
+
expect(database).to receive(:select_map).with([:key_1, :key_2, :value_field]).and_return([[1, 1, 11], [1, 2, 12], [2, 1, 21]])
|
|
58
|
+
|
|
59
|
+
expect(manager.create_cache_for_field(:value_field)).to eq [1, 1] => 11, [1, 2] => 12, [2, 1] => 21
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative '../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DSL::DatabaseDefinition do
|
|
4
|
+
|
|
5
|
+
let(:database) { Cranium::DSL::DatabaseDefinition.new "name" }
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe "#name" do
|
|
9
|
+
it "should return the name of the database definition" do
|
|
10
|
+
expect(database.name).to eq("name")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe "#connect_to" do
|
|
16
|
+
it "should set the attribute to the specified value" do
|
|
17
|
+
database.connect_to "value"
|
|
18
|
+
|
|
19
|
+
expect(database.connect_to).to eq("value")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require_relative '../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DSL::ExtractDefinition do
|
|
4
|
+
|
|
5
|
+
let(:extract) { Cranium::DSL::ExtractDefinition.new :extract_name }
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe "#name" do
|
|
9
|
+
it "should return the name of the extract definition" do
|
|
10
|
+
expect(extract.name).to eq(:extract_name)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
describe "#storage" do
|
|
16
|
+
it "should return the persistent storage corresponding to the extract" do
|
|
17
|
+
expect(extract.storage).to be_a Cranium::Extract::Storage
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
describe "#from" do
|
|
23
|
+
it "should set the attribute to the specified value" do
|
|
24
|
+
extract.from :database
|
|
25
|
+
expect(extract.from).to eq(:database)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe "#query" do
|
|
31
|
+
it "should set the attribute to the specified value" do
|
|
32
|
+
extract.query "extract query"
|
|
33
|
+
expect(extract.query).to eq("extract query")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
describe "#columns" do
|
|
39
|
+
it "should set the attribute to the specified value" do
|
|
40
|
+
extract.query %w(id name status)
|
|
41
|
+
expect(extract.query).to eq(%w(id name status))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
describe "#incrementally_by" do
|
|
47
|
+
it "should set the attribute to the specified value" do
|
|
48
|
+
extract.incrementally_by :id
|
|
49
|
+
expect(extract.incrementally_by).to eq(:id)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
describe "#last_extracted_value_of" do
|
|
55
|
+
let(:storage) { double "extract storage" }
|
|
56
|
+
before { allow(Cranium::Extract::Storage).to receive(:new).with(:extract_name).and_return(storage) }
|
|
57
|
+
|
|
58
|
+
context "when there is no last extracted value for the field" do
|
|
59
|
+
before { allow(storage).to receive(:last_value_of).with(:id).and_return(nil) }
|
|
60
|
+
|
|
61
|
+
it "should return nil" do
|
|
62
|
+
expect(extract.last_extracted_value_of(:id)).to be_nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should return the default value if one was specified" do
|
|
66
|
+
expect(extract.last_extracted_value_of(:id, 0)).to eq(0)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "should return the last extracted value of the field" do
|
|
71
|
+
allow(storage).to receive(:last_value_of).with(:id).and_return(15)
|
|
72
|
+
expect(extract.last_extracted_value_of(:id)).to eq(15)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
require_relative '../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DSL::ImportDefinition do
|
|
4
|
+
|
|
5
|
+
let(:import) { Cranium::DSL::ImportDefinition.new "import_name" }
|
|
6
|
+
|
|
7
|
+
describe "#into" do
|
|
8
|
+
it "should set the attribute to the specified value" do
|
|
9
|
+
import.into "new value"
|
|
10
|
+
|
|
11
|
+
expect(import.into).to eq("new value")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
describe "#name" do
|
|
17
|
+
it "should return the name of the import definition" do
|
|
18
|
+
expect(import.name).to eq("import_name")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
describe "#field_associations" do
|
|
24
|
+
context "when no fields are set" do
|
|
25
|
+
it "should return empty hash" do
|
|
26
|
+
expect(import.field_associations).to eq({})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
describe "#put" do
|
|
33
|
+
context "when called with a Hash" do
|
|
34
|
+
it "should store the field associations" do
|
|
35
|
+
import.put :item => :id, :title => :name
|
|
36
|
+
|
|
37
|
+
expect(import.field_associations).to eq({:item => :id, :title => :name})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "when called with a Symbol" do
|
|
42
|
+
it "should store a field association between fields with the same name" do
|
|
43
|
+
import.put :item
|
|
44
|
+
|
|
45
|
+
expect(import.field_associations).to eq({:item => :item})
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "when called with an unsupported type" do
|
|
50
|
+
it "should raise an error" do
|
|
51
|
+
expect { import.put "unsupported" }.to raise_error ArgumentError, "Unsupported argument for Import::put"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
context "when called multiple times" do
|
|
56
|
+
it "should merge all specified associations" do
|
|
57
|
+
import.put :item => :id
|
|
58
|
+
import.put :title => :name
|
|
59
|
+
import.put :category => :category, :brand => :brand
|
|
60
|
+
|
|
61
|
+
expect(import.field_associations).to eq({
|
|
62
|
+
:item => :id,
|
|
63
|
+
:title => :name,
|
|
64
|
+
:category => :category,
|
|
65
|
+
:brand => :brand
|
|
66
|
+
})
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
describe "#merge_fields" do
|
|
73
|
+
context "when no fields are set" do
|
|
74
|
+
it "should return empty hash" do
|
|
75
|
+
expect(import.merge_fields).to eq({})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
describe "#merge_on" do
|
|
82
|
+
context "when called with a Hash" do
|
|
83
|
+
it "should set merge field associations" do
|
|
84
|
+
import.merge_on :item => :id, :title => :name
|
|
85
|
+
|
|
86
|
+
expect(import.merge_fields).to eq({:item => :id, :title => :name})
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "when called with a Symbol" do
|
|
91
|
+
it "should store a merge field association between fields with the same name" do
|
|
92
|
+
import.merge_on :item
|
|
93
|
+
|
|
94
|
+
expect(import.merge_fields).to eq({:item => :item})
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context "when called with an unsupported type" do
|
|
99
|
+
it "should raise an error" do
|
|
100
|
+
expect { import.merge_on "unsupported" }.to raise_error ArgumentError, "Unsupported argument for Import::merge_on"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context "when called multiple times" do
|
|
105
|
+
it "should overwrite existing merge fields" do
|
|
106
|
+
import.merge_on :item => :id
|
|
107
|
+
import.merge_on :title => :name
|
|
108
|
+
|
|
109
|
+
expect(import.merge_fields).to eq({:title => :name})
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
describe "#delete_insert_on" do
|
|
116
|
+
|
|
117
|
+
it "should return an empty array when no value was set" do
|
|
118
|
+
expect(import.delete_insert_on).to eq([])
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
it "should store and return the value passed" do
|
|
123
|
+
import.delete_insert_on :some_field
|
|
124
|
+
|
|
125
|
+
expect(import.delete_insert_on).to eq([:some_field])
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
it "should handle multiple arguments" do
|
|
130
|
+
import.delete_insert_on :some_field, :another_field
|
|
131
|
+
|
|
132
|
+
expect(import.delete_insert_on).to eq([:some_field, :another_field])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
describe "#truncate_insert" do
|
|
139
|
+
|
|
140
|
+
it "should return false when no value was set" do
|
|
141
|
+
expect(import.truncate_insert).to eq(false)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
it "should store and return the value passed" do
|
|
146
|
+
import.truncate_insert true
|
|
147
|
+
|
|
148
|
+
expect(import.truncate_insert).to eq(true)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require_relative '../../spec_helper'
|
|
2
|
+
require 'ostruct'
|
|
3
|
+
|
|
4
|
+
describe Cranium::DSL::SourceDefinition do
|
|
5
|
+
|
|
6
|
+
let(:source) { Cranium::DSL::SourceDefinition.new "name" }
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
{ file: "name.csv",
|
|
10
|
+
delimiter: ",",
|
|
11
|
+
escape: '"',
|
|
12
|
+
quote: '"',
|
|
13
|
+
encoding: "UTF-8" }.each do |attribute, default_value|
|
|
14
|
+
|
|
15
|
+
describe "#attribute" do
|
|
16
|
+
context "if called without a parameter" do
|
|
17
|
+
it "should return the (default) value of the attribute" do
|
|
18
|
+
expect(source.send(attribute)).to eq(default_value)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "if called with a parameter" do
|
|
23
|
+
it "should set the attribute to the specified value" do
|
|
24
|
+
source.send(attribute, "new value")
|
|
25
|
+
|
|
26
|
+
expect(source.send(attribute)).to eq("new value")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
describe "#fields" do
|
|
35
|
+
it "should return an empty Hash if no fields are set" do
|
|
36
|
+
expect(source.fields).to eq({})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "should return the fields and types that were set" do
|
|
40
|
+
source.field :field1, String
|
|
41
|
+
source.field :field2, Fixnum
|
|
42
|
+
|
|
43
|
+
expect(source.fields).to eq({
|
|
44
|
+
field1: String,
|
|
45
|
+
field2: Fixnum
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
describe "#file_name_overriden?" do
|
|
52
|
+
it "should signal if the file name parameter of the source definition has been set to something other than the default" do
|
|
53
|
+
expect(source.file_name_overriden?).to be_falsey
|
|
54
|
+
|
|
55
|
+
source.file "overriden.csv"
|
|
56
|
+
expect(source.file_name_overriden?).to be_truthy
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
describe "#files" do
|
|
62
|
+
it "should return nil by default" do
|
|
63
|
+
expect(source.files).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
describe "#resolve_files" do
|
|
69
|
+
it "should store the file names of all files matching the file pattern" do
|
|
70
|
+
allow(Cranium).to receive_messages configuration: (Cranium::Configuration.new.tap do |config|
|
|
71
|
+
config.gpfdist_home_directory = "/home/gpfdist"
|
|
72
|
+
config.upload_directory = "customer"
|
|
73
|
+
end)
|
|
74
|
+
source.file "product*.csv"
|
|
75
|
+
allow(Dir).to receive(:[]).with("/home/gpfdist/customer/product*.csv").and_return(["/home/gpfdist/customer/product2.csv",
|
|
76
|
+
"/home/gpfdist/customer/product1.csv"])
|
|
77
|
+
|
|
78
|
+
source.resolve_files
|
|
79
|
+
|
|
80
|
+
expect(source.files).to eq(["product1.csv", "product2.csv"])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require_relative '../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Cranium::DSL do
|
|
4
|
+
|
|
5
|
+
let(:dsl_object) { Object.new.tap { |object| object.extend Cranium::DSL } }
|
|
6
|
+
|
|
7
|
+
describe "#database" do
|
|
8
|
+
it "should register a database connection in the application" do
|
|
9
|
+
block = lambda {}
|
|
10
|
+
|
|
11
|
+
expect(Cranium::Database).to receive(:register_database) do |arg, &blk|
|
|
12
|
+
expect(arg).to eq :name
|
|
13
|
+
expect(blk).to be block
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
dsl_object.database(:name, &block)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
describe "#source" do
|
|
22
|
+
it "should register a source in the application" do
|
|
23
|
+
expect(Cranium.application).to receive(:register_source).with(:name)
|
|
24
|
+
|
|
25
|
+
dsl_object.source(:name)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
describe "#extract" do
|
|
31
|
+
it "should create an extract definition and execute it" do
|
|
32
|
+
extract_definition = double "ExtractDefinition"
|
|
33
|
+
block = lambda {}
|
|
34
|
+
|
|
35
|
+
allow(Cranium::DSL::ExtractDefinition).to receive(:new).with(:contacts).and_return(extract_definition)
|
|
36
|
+
expect(extract_definition).to receive(:instance_eval) { |&blk| expect(blk).to be block }
|
|
37
|
+
|
|
38
|
+
extractor = double "DataExtractor"
|
|
39
|
+
allow(Cranium::Extract::DataExtractor).to receive_messages new: extractor
|
|
40
|
+
expect(extractor).to receive(:execute).with(extract_definition)
|
|
41
|
+
|
|
42
|
+
dsl_object.extract :contacts, &block
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
describe "#deduplicate" do
|
|
48
|
+
it "should call transform with correct source and target arguments" do
|
|
49
|
+
expect(dsl_object).to receive(:transform).with(:sales_items => :products)
|
|
50
|
+
dsl_object.deduplicate :sales_items, into: :products, by: [:item]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
describe "#import" do
|
|
56
|
+
it "should create an import definition and execute it" do
|
|
57
|
+
import_definition = double "ImportDefinition"
|
|
58
|
+
block = lambda {}
|
|
59
|
+
|
|
60
|
+
allow(Cranium::DSL::ImportDefinition).to receive(:new).with(:contacts).and_return(import_definition)
|
|
61
|
+
expect(import_definition).to receive(:instance_eval) { |&blk| expect(blk).to be block }
|
|
62
|
+
|
|
63
|
+
importer = double "DataImporter"
|
|
64
|
+
allow(Cranium::DataImporter).to receive_messages new: importer
|
|
65
|
+
expect(importer).to receive(:import).with(import_definition)
|
|
66
|
+
|
|
67
|
+
dsl_object.import :contacts, &block
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
describe "#archive" do
|
|
73
|
+
it "should archive files for the specified sources" do
|
|
74
|
+
allow(Cranium.application).to receive_messages sources: {first_source: double(files: ["file1", "file2"]),
|
|
75
|
+
second_source: double(files: ["file3"]),
|
|
76
|
+
third_source: double(files: ["file4"])}
|
|
77
|
+
|
|
78
|
+
expect(Cranium::Archiver).to receive(:archive).with "file1", "file2"
|
|
79
|
+
expect(Cranium::Archiver).to receive(:archive).with "file3"
|
|
80
|
+
|
|
81
|
+
dsl_object.archive :first_source, :second_source
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
describe "#remove" do
|
|
87
|
+
it "should remove files for the specified sources" do
|
|
88
|
+
allow(Cranium.application).to receive_messages sources: {first_source: double(files: ["file1", "file2"]),
|
|
89
|
+
second_source: double(files: ["file3"]),
|
|
90
|
+
third_source: double(files: ["file4"])}
|
|
91
|
+
|
|
92
|
+
expect(Cranium::Archiver).to receive(:remove).with "file1", "file2"
|
|
93
|
+
expect(Cranium::Archiver).to receive(:remove).with "file3"
|
|
94
|
+
|
|
95
|
+
dsl_object.remove :first_source, :second_source
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
describe "#sequence" do
|
|
101
|
+
it "should return a sequence with the specified name" do
|
|
102
|
+
result = dsl_object.sequence "test_sequence"
|
|
103
|
+
|
|
104
|
+
expect(result).to be_a Cranium::Transformation::Sequence
|
|
105
|
+
expect(result.name).to eq("test_sequence")
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
describe "#after" do
|
|
111
|
+
it "should register a new after hook for the application" do
|
|
112
|
+
block = -> {}
|
|
113
|
+
|
|
114
|
+
expect(Cranium.application).to receive(:register_hook).with(:after) { |&blk| expect(blk).to be block }
|
|
115
|
+
|
|
116
|
+
dsl_object.after &block
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|