csvops 0.3.0.alpha → 0.4.0.alpha
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 +4 -4
- data/README.md +56 -142
- data/docs/architecture.md +266 -0
- data/docs/release-v0.4.0-alpha.md +87 -0
- data/lib/csvtool/application/use_cases/run_cross_csv_dedupe.rb +93 -0
- data/lib/csvtool/application/use_cases/run_extraction.rb +3 -3
- data/lib/csvtool/application/use_cases/run_row_extraction.rb +3 -3
- data/lib/csvtool/application/use_cases/run_row_randomization.rb +3 -3
- data/lib/csvtool/cli.rb +5 -1
- data/lib/csvtool/domain/cross_csv_dedupe_session/column_selector.rb +44 -0
- data/lib/csvtool/domain/cross_csv_dedupe_session/cross_csv_dedupe_session.rb +46 -0
- data/lib/csvtool/domain/cross_csv_dedupe_session/csv_profile.rb +24 -0
- data/lib/csvtool/domain/cross_csv_dedupe_session/key_mapping.rb +22 -0
- data/lib/csvtool/domain/cross_csv_dedupe_session/match_options.rb +29 -0
- data/lib/csvtool/domain/row_randomization_session/randomization_source.rb +1 -0
- data/lib/csvtool/domain/row_session/row_source.rb +3 -0
- data/lib/csvtool/domain/{column_session → shared}/output_destination.rb +1 -1
- data/lib/csvtool/infrastructure/csv/cross_csv_deduper.rb +85 -0
- data/lib/csvtool/infrastructure/csv/selector_validator.rb +30 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
- data/lib/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow.rb +163 -0
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/application/use_cases/run_cross_csv_dedupe_test.rb +113 -0
- data/test/csvtool/cli_test.rb +130 -16
- data/test/csvtool/cli_unit_test.rb +16 -3
- data/test/csvtool/domain/column_session/column_session_test.rb +2 -2
- data/test/csvtool/domain/column_session/csv_source_test.rb +10 -0
- data/test/csvtool/domain/cross_csv_dedupe_session/column_selector_test.rb +42 -0
- data/test/csvtool/domain/cross_csv_dedupe_session/cross_csv_dedupe_session_test.rb +75 -0
- data/test/csvtool/domain/cross_csv_dedupe_session/csv_profile_test.rb +26 -0
- data/test/csvtool/domain/cross_csv_dedupe_session/key_mapping_test.rb +31 -0
- data/test/csvtool/domain/cross_csv_dedupe_session/match_options_test.rb +52 -0
- data/test/csvtool/domain/row_randomization_session/randomization_session_test.rb +2 -2
- data/test/csvtool/domain/row_randomization_session/randomization_source_test.rb +15 -1
- data/test/csvtool/domain/row_session/row_session_test.rb +2 -2
- data/test/csvtool/domain/row_session/row_source_test.rb +16 -0
- data/test/csvtool/domain/shared/output_destination_test.rb +24 -0
- data/test/csvtool/infrastructure/csv/cross_csv_deduper_test.rb +155 -0
- data/test/csvtool/infrastructure/csv/selector_validator_test.rb +72 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +50 -13
- data/test/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow_test.rb +246 -0
- data/test/fixtures/dedupe_reference.csv +3 -0
- data/test/fixtures/dedupe_reference.tsv +3 -0
- data/test/fixtures/dedupe_reference_all.csv +5 -0
- data/test/fixtures/dedupe_reference_no_headers.csv +2 -0
- data/test/fixtures/dedupe_reference_none.csv +2 -0
- data/test/fixtures/dedupe_reference_normalization.csv +3 -0
- data/test/fixtures/dedupe_source.csv +6 -0
- data/test/fixtures/dedupe_source.tsv +6 -0
- data/test/fixtures/dedupe_source_no_headers.csv +5 -0
- data/test/fixtures/dedupe_source_normalization.csv +4 -0
- metadata +34 -8
- data/lib/csvtool/domain/row_randomization_session/randomization_output_destination.rb +0 -31
- data/lib/csvtool/domain/row_session/row_output_destination.rb +0 -31
- data/test/csvtool/domain/column_session/output_destination_test.rb +0 -18
- data/test/csvtool/domain/row_randomization_session/randomization_output_destination_test.rb +0 -21
- data/test/csvtool/domain/row_session/row_output_destination_test.rb +0 -23
|
@@ -4,7 +4,7 @@ require_relative "../../../test_helper"
|
|
|
4
4
|
require "csvtool/domain/row_randomization_session/randomization_session"
|
|
5
5
|
require "csvtool/domain/row_randomization_session/randomization_source"
|
|
6
6
|
require "csvtool/domain/row_randomization_session/randomization_options"
|
|
7
|
-
require "csvtool/domain/
|
|
7
|
+
require "csvtool/domain/shared/output_destination"
|
|
8
8
|
|
|
9
9
|
class RandomizationSessionTest < Minitest::Test
|
|
10
10
|
def test_with_output_destination_returns_updated_session
|
|
@@ -15,7 +15,7 @@ class RandomizationSessionTest < Minitest::Test
|
|
|
15
15
|
)
|
|
16
16
|
options = Csvtool::Domain::RowRandomizationSession::RandomizationOptions.new(seed: 7)
|
|
17
17
|
session = Csvtool::Domain::RowRandomizationSession::RandomizationSession.start(source: source, options: options)
|
|
18
|
-
destination = Csvtool::Domain::
|
|
18
|
+
destination = Csvtool::Domain::Shared::OutputDestination.console
|
|
19
19
|
|
|
20
20
|
updated = session.with_output_destination(destination)
|
|
21
21
|
|
|
@@ -17,12 +17,26 @@ class RandomizationSourceTest < Minitest::Test
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def test_rejects_empty_separator
|
|
20
|
-
assert_raises(ArgumentError) do
|
|
20
|
+
error = assert_raises(ArgumentError) do
|
|
21
21
|
Csvtool::Domain::RowRandomizationSession::RandomizationSource.new(
|
|
22
22
|
path: "/tmp/a.csv",
|
|
23
23
|
separator: "",
|
|
24
24
|
headers_present: true
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
assert_equal "separator cannot be empty", error.message
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_rejects_empty_path
|
|
32
|
+
error = assert_raises(ArgumentError) do
|
|
33
|
+
Csvtool::Domain::RowRandomizationSession::RandomizationSource.new(
|
|
34
|
+
path: "",
|
|
35
|
+
separator: ",",
|
|
36
|
+
headers_present: true
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
assert_equal "path cannot be empty", error.message
|
|
27
41
|
end
|
|
28
42
|
end
|
|
@@ -4,7 +4,7 @@ require_relative "../../../test_helper"
|
|
|
4
4
|
require "csvtool/domain/row_session/row_session"
|
|
5
5
|
require "csvtool/domain/row_session/row_source"
|
|
6
6
|
require "csvtool/domain/row_session/row_range"
|
|
7
|
-
require "csvtool/domain/
|
|
7
|
+
require "csvtool/domain/shared/output_destination"
|
|
8
8
|
|
|
9
9
|
class RowSessionTest < Minitest::Test
|
|
10
10
|
def test_starts_and_sets_output_destination
|
|
@@ -12,7 +12,7 @@ class RowSessionTest < Minitest::Test
|
|
|
12
12
|
row_range = Csvtool::Domain::RowSession::RowRange.new(start_row: 1, end_row: 2)
|
|
13
13
|
|
|
14
14
|
session = Csvtool::Domain::RowSession::RowSession.start(source: source, row_range: row_range)
|
|
15
|
-
destination = Csvtool::Domain::
|
|
15
|
+
destination = Csvtool::Domain::Shared::OutputDestination.console
|
|
16
16
|
updated = session.with_output_destination(destination)
|
|
17
17
|
|
|
18
18
|
assert_equal source, updated.source
|
|
@@ -9,4 +9,20 @@ class RowSourceTest < Minitest::Test
|
|
|
9
9
|
assert_equal "/tmp/a.csv", source.path
|
|
10
10
|
assert_equal "\t", source.separator
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
def test_rejects_empty_path
|
|
14
|
+
error = assert_raises(ArgumentError) do
|
|
15
|
+
Csvtool::Domain::RowSession::RowSource.new(path: "", separator: ",")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
assert_equal "path cannot be empty", error.message
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_rejects_empty_separator
|
|
22
|
+
error = assert_raises(ArgumentError) do
|
|
23
|
+
Csvtool::Domain::RowSession::RowSource.new(path: "/tmp/a.csv", separator: "")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
assert_equal "separator cannot be empty", error.message
|
|
27
|
+
end
|
|
12
28
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/shared/output_destination"
|
|
5
|
+
|
|
6
|
+
class SharedOutputDestinationTest < Minitest::Test
|
|
7
|
+
def test_builds_console_and_file_destinations
|
|
8
|
+
console = Csvtool::Domain::Shared::OutputDestination.console
|
|
9
|
+
file = Csvtool::Domain::Shared::OutputDestination.file(path: "/tmp/out.csv")
|
|
10
|
+
|
|
11
|
+
assert_equal true, console.console?
|
|
12
|
+
assert_equal false, console.file?
|
|
13
|
+
assert_equal true, file.file?
|
|
14
|
+
assert_equal "/tmp/out.csv", file.path
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_rejects_empty_file_path
|
|
18
|
+
error = assert_raises(ArgumentError) do
|
|
19
|
+
Csvtool::Domain::Shared::OutputDestination.file(path: "")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
assert_equal "file output path cannot be empty", error.message
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/csv/cross_csv_deduper"
|
|
5
|
+
require "csvtool/domain/cross_csv_dedupe_session/column_selector"
|
|
6
|
+
require "csvtool/domain/cross_csv_dedupe_session/match_options"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
|
|
9
|
+
class InfrastructureCrossCsvDeduperTest < Minitest::Test
|
|
10
|
+
def fixture_path(name)
|
|
11
|
+
File.expand_path("../../../fixtures/#{name}", __dir__)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_filters_source_rows_by_reference_column_values
|
|
15
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
16
|
+
|
|
17
|
+
result = deduper.call(
|
|
18
|
+
source_path: fixture_path("dedupe_source.csv"),
|
|
19
|
+
reference_path: fixture_path("dedupe_reference.csv"),
|
|
20
|
+
source_selector: header_selector("customer_id"),
|
|
21
|
+
reference_selector: header_selector("external_id"),
|
|
22
|
+
source_col_sep: ",",
|
|
23
|
+
reference_col_sep: ","
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assert_equal ["customer_id", "name"], result[:headers]
|
|
27
|
+
assert_equal 5, result[:source_rows]
|
|
28
|
+
assert_equal 3, result[:removed_rows]
|
|
29
|
+
assert_equal 2, result[:kept_rows_count]
|
|
30
|
+
assert_equal [%w[1 Alice], %w[3 Cara]], result[:kept_rows]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_normalization_trim_on_case_off
|
|
34
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
35
|
+
|
|
36
|
+
result = deduper.call(
|
|
37
|
+
source_path: fixture_path("dedupe_source_normalization.csv"),
|
|
38
|
+
reference_path: fixture_path("dedupe_reference_normalization.csv"),
|
|
39
|
+
source_selector: header_selector("customer_id"),
|
|
40
|
+
reference_selector: header_selector("external_id"),
|
|
41
|
+
match_options: match_options(trim_whitespace: true, case_insensitive: false)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
assert_equal 3, result[:kept_rows_count]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_normalization_trim_on_case_on
|
|
48
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
49
|
+
|
|
50
|
+
result = deduper.call(
|
|
51
|
+
source_path: fixture_path("dedupe_source_normalization.csv"),
|
|
52
|
+
reference_path: fixture_path("dedupe_reference_normalization.csv"),
|
|
53
|
+
source_selector: header_selector("customer_id"),
|
|
54
|
+
reference_selector: header_selector("external_id"),
|
|
55
|
+
match_options: match_options(trim_whitespace: true, case_insensitive: true)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert_equal 1, result[:kept_rows_count]
|
|
59
|
+
assert_equal [%w[B2 Bob]], result[:kept_rows]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_normalization_trim_off_case_on
|
|
63
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
64
|
+
|
|
65
|
+
result = deduper.call(
|
|
66
|
+
source_path: fixture_path("dedupe_source_normalization.csv"),
|
|
67
|
+
reference_path: fixture_path("dedupe_reference_normalization.csv"),
|
|
68
|
+
source_selector: header_selector("customer_id"),
|
|
69
|
+
reference_selector: header_selector("external_id"),
|
|
70
|
+
match_options: match_options(trim_whitespace: false, case_insensitive: true)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
assert_equal 2, result[:kept_rows_count]
|
|
74
|
+
assert_equal [[" A1 ", "Alice"], %w[B2 Bob]], result[:kept_rows]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_normalization_trim_off_case_off
|
|
78
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
79
|
+
|
|
80
|
+
result = deduper.call(
|
|
81
|
+
source_path: fixture_path("dedupe_source_normalization.csv"),
|
|
82
|
+
reference_path: fixture_path("dedupe_reference_normalization.csv"),
|
|
83
|
+
source_selector: header_selector("customer_id"),
|
|
84
|
+
reference_selector: header_selector("external_id"),
|
|
85
|
+
match_options: match_options(trim_whitespace: false, case_insensitive: false)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
assert_equal 3, result[:kept_rows_count]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_each_retained_streams_rows_and_reports_stats
|
|
92
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
93
|
+
yielded_rows = []
|
|
94
|
+
|
|
95
|
+
result = deduper.each_retained(
|
|
96
|
+
source_path: fixture_path("dedupe_source.csv"),
|
|
97
|
+
reference_path: fixture_path("dedupe_reference.csv"),
|
|
98
|
+
source_selector: header_selector("customer_id"),
|
|
99
|
+
reference_selector: header_selector("external_id")
|
|
100
|
+
) { |fields| yielded_rows << fields }
|
|
101
|
+
|
|
102
|
+
assert_equal [%w[1 Alice], %w[3 Cara]], yielded_rows
|
|
103
|
+
assert_equal 5, result[:source_rows]
|
|
104
|
+
assert_equal 3, result[:removed_rows]
|
|
105
|
+
assert_equal 2, result[:kept_rows_count]
|
|
106
|
+
refute_includes result.keys, :kept_rows
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_each_retained_supports_large_inputs_with_streaming
|
|
110
|
+
deduper = Csvtool::Infrastructure::CSV::CrossCsvDeduper.new
|
|
111
|
+
|
|
112
|
+
Dir.mktmpdir do |dir|
|
|
113
|
+
source_path = File.join(dir, "source.csv")
|
|
114
|
+
reference_path = File.join(dir, "reference.csv")
|
|
115
|
+
|
|
116
|
+
File.open(source_path, "w") do |file|
|
|
117
|
+
file.puts "id,name"
|
|
118
|
+
10_000.times { |index| file.puts "#{index},name#{index}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
File.open(reference_path, "w") do |file|
|
|
122
|
+
file.puts "external_id"
|
|
123
|
+
10_000.times do |index|
|
|
124
|
+
file.puts index.to_s if (index % 2).zero?
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
yielded_count = 0
|
|
129
|
+
result = deduper.each_retained(
|
|
130
|
+
source_path: source_path,
|
|
131
|
+
reference_path: reference_path,
|
|
132
|
+
source_selector: header_selector("id"),
|
|
133
|
+
reference_selector: header_selector("external_id")
|
|
134
|
+
) { |_fields| yielded_count += 1 }
|
|
135
|
+
|
|
136
|
+
assert_equal 10_000, result[:source_rows]
|
|
137
|
+
assert_equal 5_000, result[:removed_rows]
|
|
138
|
+
assert_equal 5_000, result[:kept_rows_count]
|
|
139
|
+
assert_equal 5_000, yielded_count
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def header_selector(name)
|
|
146
|
+
Csvtool::Domain::CrossCsvDedupeSession::ColumnSelector.from_input(headers_present: true, input: name)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def match_options(trim_whitespace:, case_insensitive:)
|
|
150
|
+
Csvtool::Domain::CrossCsvDedupeSession::MatchOptions.new(
|
|
151
|
+
trim_whitespace: trim_whitespace,
|
|
152
|
+
case_insensitive: case_insensitive
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/csv/selector_validator"
|
|
5
|
+
require "csvtool/domain/cross_csv_dedupe_session/csv_profile"
|
|
6
|
+
require "csvtool/domain/cross_csv_dedupe_session/column_selector"
|
|
7
|
+
|
|
8
|
+
class InfrastructureSelectorValidatorTest < Minitest::Test
|
|
9
|
+
def fixture_path(name)
|
|
10
|
+
File.expand_path("../../../fixtures/#{name}", __dir__)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_accepts_header_selector_when_column_exists
|
|
14
|
+
validator = Csvtool::Infrastructure::CSV::SelectorValidator.new
|
|
15
|
+
profile = Csvtool::Domain::CrossCsvDedupeSession::CsvProfile.new(
|
|
16
|
+
path: fixture_path("dedupe_source.csv"),
|
|
17
|
+
separator: ",",
|
|
18
|
+
headers_present: true
|
|
19
|
+
)
|
|
20
|
+
selector = Csvtool::Domain::CrossCsvDedupeSession::ColumnSelector.from_input(
|
|
21
|
+
headers_present: true,
|
|
22
|
+
input: "customer_id"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
assert_equal true, validator.valid?(profile: profile, selector: selector)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_rejects_header_selector_when_column_missing
|
|
29
|
+
validator = Csvtool::Infrastructure::CSV::SelectorValidator.new
|
|
30
|
+
profile = Csvtool::Domain::CrossCsvDedupeSession::CsvProfile.new(
|
|
31
|
+
path: fixture_path("dedupe_source.csv"),
|
|
32
|
+
separator: ",",
|
|
33
|
+
headers_present: true
|
|
34
|
+
)
|
|
35
|
+
selector = Csvtool::Domain::CrossCsvDedupeSession::ColumnSelector.from_input(
|
|
36
|
+
headers_present: true,
|
|
37
|
+
input: "missing"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
assert_equal false, validator.valid?(profile: profile, selector: selector)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_accepts_index_selector_when_in_range
|
|
44
|
+
validator = Csvtool::Infrastructure::CSV::SelectorValidator.new
|
|
45
|
+
profile = Csvtool::Domain::CrossCsvDedupeSession::CsvProfile.new(
|
|
46
|
+
path: fixture_path("dedupe_source_no_headers.csv"),
|
|
47
|
+
separator: ",",
|
|
48
|
+
headers_present: false
|
|
49
|
+
)
|
|
50
|
+
selector = Csvtool::Domain::CrossCsvDedupeSession::ColumnSelector.from_input(
|
|
51
|
+
headers_present: false,
|
|
52
|
+
input: "2"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert_equal true, validator.valid?(profile: profile, selector: selector)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_rejects_index_selector_when_out_of_range
|
|
59
|
+
validator = Csvtool::Infrastructure::CSV::SelectorValidator.new
|
|
60
|
+
profile = Csvtool::Domain::CrossCsvDedupeSession::CsvProfile.new(
|
|
61
|
+
path: fixture_path("dedupe_source_no_headers.csv"),
|
|
62
|
+
separator: ",",
|
|
63
|
+
headers_present: false
|
|
64
|
+
)
|
|
65
|
+
selector = Csvtool::Domain::CrossCsvDedupeSession::ColumnSelector.from_input(
|
|
66
|
+
headers_present: false,
|
|
67
|
+
input: "9"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
assert_equal false, validator.valid?(profile: profile, selector: selector)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -20,14 +20,16 @@ class MenuLoopTest < Minitest::Test
|
|
|
20
20
|
column_action = FakeAction.new
|
|
21
21
|
rows_action = FakeAction.new
|
|
22
22
|
randomize_rows_action = FakeAction.new
|
|
23
|
+
dedupe_action = FakeAction.new
|
|
23
24
|
stdout = StringIO.new
|
|
24
25
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
25
|
-
stdin: StringIO.new("1\
|
|
26
|
+
stdin: StringIO.new("1\n5\n"),
|
|
26
27
|
stdout: stdout,
|
|
27
|
-
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
28
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Dedupe using another CSV", "Exit"],
|
|
28
29
|
extract_column_action: column_action,
|
|
29
30
|
extract_rows_action: rows_action,
|
|
30
|
-
randomize_rows_action: randomize_rows_action
|
|
31
|
+
randomize_rows_action: randomize_rows_action,
|
|
32
|
+
dedupe_action: dedupe_action
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
status = menu.run
|
|
@@ -36,6 +38,7 @@ class MenuLoopTest < Minitest::Test
|
|
|
36
38
|
assert_equal 1, column_action.runs
|
|
37
39
|
assert_equal 0, rows_action.runs
|
|
38
40
|
assert_equal 0, randomize_rows_action.runs
|
|
41
|
+
assert_equal 0, dedupe_action.runs
|
|
39
42
|
assert_includes stdout.string, "CSV Tool Menu"
|
|
40
43
|
end
|
|
41
44
|
|
|
@@ -43,14 +46,16 @@ class MenuLoopTest < Minitest::Test
|
|
|
43
46
|
column_action = FakeAction.new
|
|
44
47
|
rows_action = FakeAction.new
|
|
45
48
|
randomize_rows_action = FakeAction.new
|
|
49
|
+
dedupe_action = FakeAction.new
|
|
46
50
|
stdout = StringIO.new
|
|
47
51
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
48
|
-
stdin: StringIO.new("2\
|
|
52
|
+
stdin: StringIO.new("2\n5\n"),
|
|
49
53
|
stdout: stdout,
|
|
50
|
-
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
54
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Dedupe using another CSV", "Exit"],
|
|
51
55
|
extract_column_action: column_action,
|
|
52
56
|
extract_rows_action: rows_action,
|
|
53
|
-
randomize_rows_action: randomize_rows_action
|
|
57
|
+
randomize_rows_action: randomize_rows_action,
|
|
58
|
+
dedupe_action: dedupe_action
|
|
54
59
|
)
|
|
55
60
|
|
|
56
61
|
status = menu.run
|
|
@@ -59,20 +64,23 @@ class MenuLoopTest < Minitest::Test
|
|
|
59
64
|
assert_equal 0, column_action.runs
|
|
60
65
|
assert_equal 1, rows_action.runs
|
|
61
66
|
assert_equal 0, randomize_rows_action.runs
|
|
67
|
+
assert_equal 0, dedupe_action.runs
|
|
62
68
|
end
|
|
63
69
|
|
|
64
70
|
def test_routes_randomize_rows_then_exit
|
|
65
71
|
column_action = FakeAction.new
|
|
66
72
|
rows_action = FakeAction.new
|
|
67
73
|
randomize_rows_action = FakeAction.new
|
|
74
|
+
dedupe_action = FakeAction.new
|
|
68
75
|
stdout = StringIO.new
|
|
69
76
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
70
|
-
stdin: StringIO.new("3\
|
|
77
|
+
stdin: StringIO.new("3\n5\n"),
|
|
71
78
|
stdout: stdout,
|
|
72
|
-
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
79
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Dedupe using another CSV", "Exit"],
|
|
73
80
|
extract_column_action: column_action,
|
|
74
81
|
extract_rows_action: rows_action,
|
|
75
|
-
randomize_rows_action: randomize_rows_action
|
|
82
|
+
randomize_rows_action: randomize_rows_action,
|
|
83
|
+
dedupe_action: dedupe_action
|
|
76
84
|
)
|
|
77
85
|
|
|
78
86
|
status = menu.run
|
|
@@ -81,27 +89,56 @@ class MenuLoopTest < Minitest::Test
|
|
|
81
89
|
assert_equal 0, column_action.runs
|
|
82
90
|
assert_equal 0, rows_action.runs
|
|
83
91
|
assert_equal 1, randomize_rows_action.runs
|
|
92
|
+
assert_equal 0, dedupe_action.runs
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_routes_dedupe_then_exit
|
|
96
|
+
column_action = FakeAction.new
|
|
97
|
+
rows_action = FakeAction.new
|
|
98
|
+
randomize_rows_action = FakeAction.new
|
|
99
|
+
dedupe_action = FakeAction.new
|
|
100
|
+
stdout = StringIO.new
|
|
101
|
+
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
102
|
+
stdin: StringIO.new("4\n5\n"),
|
|
103
|
+
stdout: stdout,
|
|
104
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Dedupe using another CSV", "Exit"],
|
|
105
|
+
extract_column_action: column_action,
|
|
106
|
+
extract_rows_action: rows_action,
|
|
107
|
+
randomize_rows_action: randomize_rows_action,
|
|
108
|
+
dedupe_action: dedupe_action
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
status = menu.run
|
|
112
|
+
|
|
113
|
+
assert_equal 0, status
|
|
114
|
+
assert_equal 0, column_action.runs
|
|
115
|
+
assert_equal 0, rows_action.runs
|
|
116
|
+
assert_equal 0, randomize_rows_action.runs
|
|
117
|
+
assert_equal 1, dedupe_action.runs
|
|
84
118
|
end
|
|
85
119
|
|
|
86
120
|
def test_invalid_choice_shows_prompt
|
|
87
121
|
column_action = FakeAction.new
|
|
88
122
|
rows_action = FakeAction.new
|
|
89
123
|
randomize_rows_action = FakeAction.new
|
|
124
|
+
dedupe_action = FakeAction.new
|
|
90
125
|
stdout = StringIO.new
|
|
91
126
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
92
|
-
stdin: StringIO.new("x\
|
|
127
|
+
stdin: StringIO.new("x\n5\n"),
|
|
93
128
|
stdout: stdout,
|
|
94
|
-
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
129
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Dedupe using another CSV", "Exit"],
|
|
95
130
|
extract_column_action: column_action,
|
|
96
131
|
extract_rows_action: rows_action,
|
|
97
|
-
randomize_rows_action: randomize_rows_action
|
|
132
|
+
randomize_rows_action: randomize_rows_action,
|
|
133
|
+
dedupe_action: dedupe_action
|
|
98
134
|
)
|
|
99
135
|
|
|
100
136
|
menu.run
|
|
101
137
|
|
|
102
|
-
assert_includes stdout.string, "Please choose 1, 2, 3, or
|
|
138
|
+
assert_includes stdout.string, "Please choose 1, 2, 3, 4, or 5."
|
|
103
139
|
assert_equal 0, column_action.runs
|
|
104
140
|
assert_equal 0, rows_action.runs
|
|
105
141
|
assert_equal 0, randomize_rows_action.runs
|
|
142
|
+
assert_equal 0, dedupe_action.runs
|
|
106
143
|
end
|
|
107
144
|
end
|