csvops 0.1.0.alpha → 0.3.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 +83 -10
- data/docs/release-v0.2.0-alpha.md +80 -0
- data/docs/release-v0.3.0-alpha.md +74 -0
- data/lib/csvtool/application/use_cases/run_extraction.rb +17 -17
- data/lib/csvtool/application/use_cases/run_row_extraction.rb +111 -0
- data/lib/csvtool/application/use_cases/run_row_randomization.rb +105 -0
- data/lib/csvtool/cli.rb +10 -2
- data/lib/csvtool/domain/{extraction_session → column_session}/column_selection.rb +1 -1
- data/lib/csvtool/domain/{extraction_session/extraction_session.rb → column_session/column_session.rb} +2 -2
- data/lib/csvtool/domain/{extraction_session → column_session}/csv_source.rb +1 -1
- data/lib/csvtool/domain/{extraction_session → column_session}/extraction_options.rb +1 -1
- data/lib/csvtool/domain/{extraction_session → column_session}/extraction_value.rb +1 -1
- data/lib/csvtool/domain/{extraction_session → column_session}/output_destination.rb +1 -1
- data/lib/csvtool/domain/{extraction_session → column_session}/preview.rb +1 -1
- data/lib/csvtool/domain/{extraction_session → column_session}/separator.rb +1 -1
- data/lib/csvtool/domain/row_randomization_session/randomization_options.rb +17 -0
- data/lib/csvtool/domain/row_randomization_session/randomization_output_destination.rb +31 -0
- data/lib/csvtool/domain/row_randomization_session/randomization_session.rb +25 -0
- data/lib/csvtool/domain/row_randomization_session/randomization_source.rb +23 -0
- data/lib/csvtool/domain/row_session/row_output_destination.rb +31 -0
- data/lib/csvtool/domain/row_session/row_range.rb +39 -0
- data/lib/csvtool/domain/row_session/row_session.rb +25 -0
- data/lib/csvtool/domain/row_session/row_source.rb +16 -0
- data/lib/csvtool/infrastructure/csv/row_randomizer.rb +83 -0
- data/lib/csvtool/infrastructure/csv/row_streamer.rb +27 -0
- data/lib/csvtool/infrastructure/output/csv_row_console_writer.rb +34 -0
- data/lib/csvtool/infrastructure/output/csv_row_file_writer.rb +45 -0
- data/lib/csvtool/interface/cli/errors/presenter.rb +20 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +13 -5
- data/lib/csvtool/interface/cli/prompts/headers_present_prompt.rb +22 -0
- data/lib/csvtool/interface/cli/prompts/seed_prompt.rb +29 -0
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/application/use_cases/run_row_extraction_test.rb +140 -0
- data/test/csvtool/application/use_cases/run_row_randomization_test.rb +124 -0
- data/test/csvtool/cli_test.rb +237 -6
- data/test/csvtool/cli_unit_test.rb +24 -1
- data/test/csvtool/domain/{extraction_session → column_session}/column_selection_test.rb +2 -2
- data/test/csvtool/domain/column_session/column_session_test.rb +35 -0
- data/test/csvtool/domain/column_session/csv_source_test.rb +14 -0
- data/test/csvtool/domain/{extraction_session → column_session}/extraction_options_test.rb +3 -3
- data/test/csvtool/domain/{extraction_session → column_session}/extraction_value_test.rb +2 -2
- data/test/csvtool/domain/{extraction_session → column_session}/output_destination_test.rb +3 -3
- data/test/csvtool/domain/column_session/preview_test.rb +18 -0
- data/test/csvtool/domain/{extraction_session → column_session}/separator_test.rb +3 -3
- data/test/csvtool/domain/row_randomization_session/randomization_options_test.rb +20 -0
- data/test/csvtool/domain/row_randomization_session/randomization_output_destination_test.rb +21 -0
- data/test/csvtool/domain/row_randomization_session/randomization_session_test.rb +26 -0
- data/test/csvtool/domain/row_randomization_session/randomization_source_test.rb +28 -0
- data/test/csvtool/domain/row_session/row_output_destination_test.rb +23 -0
- data/test/csvtool/domain/row_session/row_range_test.rb +30 -0
- data/test/csvtool/domain/row_session/row_session_test.rb +22 -0
- data/test/csvtool/domain/row_session/row_source_test.rb +12 -0
- data/test/csvtool/infrastructure/csv/row_randomizer_test.rb +37 -0
- data/test/csvtool/infrastructure/csv/row_streamer_test.rb +41 -0
- data/test/csvtool/infrastructure/output/csv_row_console_writer_test.rb +24 -0
- data/test/csvtool/infrastructure/output/csv_row_file_writer_test.rb +40 -0
- data/test/csvtool/interface/cli/errors/presenter_test.rb +10 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +68 -12
- data/test/csvtool/interface/cli/prompts/headers_present_prompt_test.rb +14 -0
- data/test/csvtool/interface/cli/prompts/seed_prompt_test.rb +39 -0
- data/test/fixtures/sample_people_bad_tail.csv +5 -0
- data/test/fixtures/sample_people_no_headers.csv +3 -0
- metadata +53 -17
- data/test/csvtool/domain/extraction_session/csv_source_test.rb +0 -14
- data/test/csvtool/domain/extraction_session/extraction_session_test.rb +0 -35
- data/test/csvtool/domain/extraction_session/preview_test.rb +0 -18
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../../test_helper"
|
|
4
|
-
require "csvtool/domain/
|
|
4
|
+
require "csvtool/domain/column_session/extraction_value"
|
|
5
5
|
|
|
6
6
|
class ExtractionValueTest < Minitest::Test
|
|
7
7
|
def test_stringifies_value
|
|
8
|
-
value = Csvtool::Domain::
|
|
8
|
+
value = Csvtool::Domain::ColumnSession::ExtractionValue.new(123)
|
|
9
9
|
assert_equal "123", value.value
|
|
10
10
|
end
|
|
11
11
|
end
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../../test_helper"
|
|
4
|
-
require "csvtool/domain/
|
|
4
|
+
require "csvtool/domain/column_session/output_destination"
|
|
5
5
|
|
|
6
6
|
class OutputDestinationTest < Minitest::Test
|
|
7
7
|
def test_console_factory
|
|
8
|
-
destination = Csvtool::Domain::
|
|
8
|
+
destination = Csvtool::Domain::ColumnSession::OutputDestination.console
|
|
9
9
|
assert_equal true, destination.console?
|
|
10
10
|
assert_equal false, destination.file?
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def test_file_factory
|
|
14
|
-
destination = Csvtool::Domain::
|
|
14
|
+
destination = Csvtool::Domain::ColumnSession::OutputDestination.file(path: "/tmp/out.csv")
|
|
15
15
|
assert_equal true, destination.file?
|
|
16
16
|
assert_equal "/tmp/out.csv", destination.path
|
|
17
17
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/column_session/preview"
|
|
5
|
+
require "csvtool/domain/column_session/extraction_value"
|
|
6
|
+
|
|
7
|
+
class PreviewTest < Minitest::Test
|
|
8
|
+
def test_exposes_size_and_string_values
|
|
9
|
+
values = [
|
|
10
|
+
Csvtool::Domain::ColumnSession::ExtractionValue.new("Alice"),
|
|
11
|
+
Csvtool::Domain::ColumnSession::ExtractionValue.new("Bob")
|
|
12
|
+
]
|
|
13
|
+
preview = Csvtool::Domain::ColumnSession::Preview.new(values: values)
|
|
14
|
+
|
|
15
|
+
assert_equal 2, preview.size
|
|
16
|
+
assert_equal %w[Alice Bob], preview.to_strings
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../../test_helper"
|
|
4
|
-
require "csvtool/domain/
|
|
4
|
+
require "csvtool/domain/column_session/separator"
|
|
5
5
|
|
|
6
6
|
class SeparatorTest < Minitest::Test
|
|
7
7
|
def test_stores_value
|
|
8
|
-
separator = Csvtool::Domain::
|
|
8
|
+
separator = Csvtool::Domain::ColumnSession::Separator.new(",")
|
|
9
9
|
assert_equal ",", separator.value
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def test_empty_value_raises
|
|
13
|
-
assert_raises(ArgumentError) { Csvtool::Domain::
|
|
13
|
+
assert_raises(ArgumentError) { Csvtool::Domain::ColumnSession::Separator.new("") }
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_randomization_session/randomization_options"
|
|
5
|
+
|
|
6
|
+
class RandomizationOptionsTest < Minitest::Test
|
|
7
|
+
def test_accepts_nil_or_integer_seed
|
|
8
|
+
with_seed = Csvtool::Domain::RowRandomizationSession::RandomizationOptions.new(seed: 42)
|
|
9
|
+
without_seed = Csvtool::Domain::RowRandomizationSession::RandomizationOptions.new(seed: nil)
|
|
10
|
+
|
|
11
|
+
assert_equal 42, with_seed.seed
|
|
12
|
+
assert_nil without_seed.seed
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_rejects_non_integer_seed
|
|
16
|
+
assert_raises(ArgumentError) do
|
|
17
|
+
Csvtool::Domain::RowRandomizationSession::RandomizationOptions.new(seed: "abc")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_randomization_session/randomization_output_destination"
|
|
5
|
+
|
|
6
|
+
class RandomizationOutputDestinationTest < Minitest::Test
|
|
7
|
+
def test_console_and_file_modes
|
|
8
|
+
console = Csvtool::Domain::RowRandomizationSession::RandomizationOutputDestination.console
|
|
9
|
+
file = Csvtool::Domain::RowRandomizationSession::RandomizationOutputDestination.file(path: "/tmp/out.csv")
|
|
10
|
+
|
|
11
|
+
assert_equal false, console.file?
|
|
12
|
+
assert_equal true, file.file?
|
|
13
|
+
assert_equal "/tmp/out.csv", file.path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_rejects_empty_file_path
|
|
17
|
+
assert_raises(ArgumentError) do
|
|
18
|
+
Csvtool::Domain::RowRandomizationSession::RandomizationOutputDestination.file(path: "")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_randomization_session/randomization_session"
|
|
5
|
+
require "csvtool/domain/row_randomization_session/randomization_source"
|
|
6
|
+
require "csvtool/domain/row_randomization_session/randomization_options"
|
|
7
|
+
require "csvtool/domain/row_randomization_session/randomization_output_destination"
|
|
8
|
+
|
|
9
|
+
class RandomizationSessionTest < Minitest::Test
|
|
10
|
+
def test_with_output_destination_returns_updated_session
|
|
11
|
+
source = Csvtool::Domain::RowRandomizationSession::RandomizationSource.new(
|
|
12
|
+
path: "/tmp/in.csv",
|
|
13
|
+
separator: ",",
|
|
14
|
+
headers_present: true
|
|
15
|
+
)
|
|
16
|
+
options = Csvtool::Domain::RowRandomizationSession::RandomizationOptions.new(seed: 7)
|
|
17
|
+
session = Csvtool::Domain::RowRandomizationSession::RandomizationSession.start(source: source, options: options)
|
|
18
|
+
destination = Csvtool::Domain::RowRandomizationSession::RandomizationOutputDestination.console
|
|
19
|
+
|
|
20
|
+
updated = session.with_output_destination(destination)
|
|
21
|
+
|
|
22
|
+
assert_equal source, updated.source
|
|
23
|
+
assert_equal options, updated.options
|
|
24
|
+
assert_equal destination, updated.output_destination
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_randomization_session/randomization_source"
|
|
5
|
+
|
|
6
|
+
class RandomizationSourceTest < Minitest::Test
|
|
7
|
+
def test_holds_path_separator_and_headers_mode
|
|
8
|
+
source = Csvtool::Domain::RowRandomizationSession::RandomizationSource.new(
|
|
9
|
+
path: "/tmp/a.csv",
|
|
10
|
+
separator: ",",
|
|
11
|
+
headers_present: true
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
assert_equal "/tmp/a.csv", source.path
|
|
15
|
+
assert_equal ",", source.separator
|
|
16
|
+
assert_equal true, source.headers_present?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_rejects_empty_separator
|
|
20
|
+
assert_raises(ArgumentError) do
|
|
21
|
+
Csvtool::Domain::RowRandomizationSession::RandomizationSource.new(
|
|
22
|
+
path: "/tmp/a.csv",
|
|
23
|
+
separator: "",
|
|
24
|
+
headers_present: true
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_session/row_output_destination"
|
|
5
|
+
|
|
6
|
+
class RowRangeOutputDestinationTest < Minitest::Test
|
|
7
|
+
def test_console_destination
|
|
8
|
+
destination = Csvtool::Domain::RowSession::RowOutputDestination.console
|
|
9
|
+
refute destination.file?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_file_destination
|
|
13
|
+
destination = Csvtool::Domain::RowSession::RowOutputDestination.file(path: "/tmp/out.csv")
|
|
14
|
+
assert destination.file?
|
|
15
|
+
assert_equal "/tmp/out.csv", destination.path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_rejects_empty_file_path
|
|
19
|
+
assert_raises(ArgumentError) do
|
|
20
|
+
Csvtool::Domain::RowSession::RowOutputDestination.file(path: "")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_session/row_range"
|
|
5
|
+
|
|
6
|
+
class RowRangeTest < Minitest::Test
|
|
7
|
+
def test_builds_from_valid_inputs
|
|
8
|
+
row_range = Csvtool::Domain::RowSession::RowRange.from_inputs(start_row_input: "2", end_row_input: "4")
|
|
9
|
+
assert_equal 2, row_range.start_row
|
|
10
|
+
assert_equal 4, row_range.end_row
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_rejects_invalid_start_row
|
|
14
|
+
assert_raises(Csvtool::Domain::RowSession::InvalidStartRowError) do
|
|
15
|
+
Csvtool::Domain::RowSession::RowRange.from_inputs(start_row_input: "0", end_row_input: "2")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_rejects_invalid_end_row
|
|
20
|
+
assert_raises(Csvtool::Domain::RowSession::InvalidEndRowError) do
|
|
21
|
+
Csvtool::Domain::RowSession::RowRange.from_inputs(start_row_input: "1", end_row_input: "abc")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_rejects_end_before_start
|
|
26
|
+
assert_raises(Csvtool::Domain::RowSession::InvalidRowRangeOrderError) do
|
|
27
|
+
Csvtool::Domain::RowSession::RowRange.from_inputs(start_row_input: "3", end_row_input: "2")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_session/row_session"
|
|
5
|
+
require "csvtool/domain/row_session/row_source"
|
|
6
|
+
require "csvtool/domain/row_session/row_range"
|
|
7
|
+
require "csvtool/domain/row_session/row_output_destination"
|
|
8
|
+
|
|
9
|
+
class RowSessionTest < Minitest::Test
|
|
10
|
+
def test_starts_and_sets_output_destination
|
|
11
|
+
source = Csvtool::Domain::RowSession::RowSource.new(path: "/tmp/a.csv", separator: ",")
|
|
12
|
+
row_range = Csvtool::Domain::RowSession::RowRange.new(start_row: 1, end_row: 2)
|
|
13
|
+
|
|
14
|
+
session = Csvtool::Domain::RowSession::RowSession.start(source: source, row_range: row_range)
|
|
15
|
+
destination = Csvtool::Domain::RowSession::RowOutputDestination.console
|
|
16
|
+
updated = session.with_output_destination(destination)
|
|
17
|
+
|
|
18
|
+
assert_equal source, updated.source
|
|
19
|
+
assert_equal row_range, updated.row_range
|
|
20
|
+
assert_equal destination, updated.output_destination
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/domain/row_session/row_source"
|
|
5
|
+
|
|
6
|
+
class RowSourceTest < Minitest::Test
|
|
7
|
+
def test_holds_path_and_separator
|
|
8
|
+
source = Csvtool::Domain::RowSession::RowSource.new(path: "/tmp/a.csv", separator: "\t")
|
|
9
|
+
assert_equal "/tmp/a.csv", source.path
|
|
10
|
+
assert_equal "\t", source.separator
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/csv/row_randomizer"
|
|
5
|
+
|
|
6
|
+
class InfrastructureRowRandomizerTest < Minitest::Test
|
|
7
|
+
def fixture_path(name)
|
|
8
|
+
File.expand_path("../../../fixtures/#{name}", __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_randomizes_rows_and_preserves_membership
|
|
12
|
+
randomizer = Csvtool::Infrastructure::CSV::RowRandomizer.new
|
|
13
|
+
|
|
14
|
+
rows = randomizer.call(file_path: fixture_path("sample_people.csv"), col_sep: ",", headers: true, seed: 1234)
|
|
15
|
+
|
|
16
|
+
assert_equal 3, rows.length
|
|
17
|
+
assert_equal [%w[Alice London], %w[Bob Paris], %w[Cara Berlin]].sort, rows.sort
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_same_seed_returns_same_order
|
|
21
|
+
randomizer = Csvtool::Infrastructure::CSV::RowRandomizer.new
|
|
22
|
+
|
|
23
|
+
one = randomizer.call(file_path: fixture_path("sample_people_many.csv"), col_sep: ",", headers: true, seed: 42)
|
|
24
|
+
two = randomizer.call(file_path: fixture_path("sample_people_many.csv"), col_sep: ",", headers: true, seed: 42)
|
|
25
|
+
|
|
26
|
+
assert_equal one, two
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_different_seed_changes_order
|
|
30
|
+
randomizer = Csvtool::Infrastructure::CSV::RowRandomizer.new
|
|
31
|
+
|
|
32
|
+
one = randomizer.call(file_path: fixture_path("sample_people_many.csv"), col_sep: ",", headers: true, seed: 42)
|
|
33
|
+
two = randomizer.call(file_path: fixture_path("sample_people_many.csv"), col_sep: ",", headers: true, seed: 43)
|
|
34
|
+
|
|
35
|
+
refute_equal one, two
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/csv/row_streamer"
|
|
5
|
+
|
|
6
|
+
class InfrastructureRowStreamerTest < Minitest::Test
|
|
7
|
+
def fixture_path(name)
|
|
8
|
+
File.expand_path("../../../fixtures/#{name}", __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_streams_only_requested_row_range
|
|
12
|
+
streamer = Csvtool::Infrastructure::CSV::RowStreamer.new
|
|
13
|
+
rows = []
|
|
14
|
+
|
|
15
|
+
stats = streamer.each_in_range(
|
|
16
|
+
file_path: fixture_path("sample_people.csv"),
|
|
17
|
+
col_sep: ",",
|
|
18
|
+
start_row: 2,
|
|
19
|
+
end_row: 3
|
|
20
|
+
) { |fields| rows << fields }
|
|
21
|
+
|
|
22
|
+
assert_equal [["Bob", "Paris"], ["Cara", "Berlin"]], rows
|
|
23
|
+
assert_equal true, stats[:matched]
|
|
24
|
+
assert_equal 3, stats[:row_count]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_stops_before_malformed_tail_when_end_row_reached
|
|
28
|
+
streamer = Csvtool::Infrastructure::CSV::RowStreamer.new
|
|
29
|
+
rows = []
|
|
30
|
+
|
|
31
|
+
stats = streamer.each_in_range(
|
|
32
|
+
file_path: fixture_path("sample_people_bad_tail.csv"),
|
|
33
|
+
col_sep: ",",
|
|
34
|
+
start_row: 1,
|
|
35
|
+
end_row: 2
|
|
36
|
+
) { |fields| rows << fields }
|
|
37
|
+
|
|
38
|
+
assert_equal [["Alice", "London"], ["Bob", "Paris"]], rows
|
|
39
|
+
assert_equal true, stats[:matched]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/output/csv_row_console_writer"
|
|
5
|
+
|
|
6
|
+
class InfrastructureCsvRowConsoleWriterTest < Minitest::Test
|
|
7
|
+
class FakeRowStreamer
|
|
8
|
+
def each_in_range(file_path:, col_sep:, start_row:, end_row:)
|
|
9
|
+
yield ["Bob", "Paris"]
|
|
10
|
+
yield ["Cara", "Berlin"]
|
|
11
|
+
{ matched: true, row_count: 3 }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_writes_header_and_rows_to_stdout
|
|
16
|
+
out = StringIO.new
|
|
17
|
+
writer = Csvtool::Infrastructure::Output::CsvRowConsoleWriter.new(stdout: out, row_streamer: FakeRowStreamer.new)
|
|
18
|
+
|
|
19
|
+
stats = writer.call(file_path: "x.csv", col_sep: ",", headers: ["name", "city"], start_row: 2, end_row: 3)
|
|
20
|
+
|
|
21
|
+
assert_equal "name,city\nBob,Paris\nCara,Berlin\n", out.string
|
|
22
|
+
assert_equal true, stats[:matched]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/infrastructure/output/csv_row_file_writer"
|
|
5
|
+
require "csvtool/interface/cli/errors/presenter"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
|
|
8
|
+
class InfrastructureCsvRowFileWriterTest < Minitest::Test
|
|
9
|
+
class FakeRowStreamer
|
|
10
|
+
def each_in_range(file_path:, col_sep:, start_row:, end_row:)
|
|
11
|
+
yield ["Bob", "Paris"]
|
|
12
|
+
yield ["Cara", "Berlin"]
|
|
13
|
+
{ matched: true, row_count: 3 }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_writes_header_and_rows_to_file
|
|
18
|
+
stdout = StringIO.new
|
|
19
|
+
writer = Csvtool::Infrastructure::Output::CsvRowFileWriter.new(
|
|
20
|
+
stdout: stdout,
|
|
21
|
+
errors: Csvtool::Interface::CLI::Errors::Presenter.new(stdout: stdout),
|
|
22
|
+
row_streamer: FakeRowStreamer.new
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Dir.mktmpdir do |dir|
|
|
26
|
+
output_path = File.join(dir, "rows.csv")
|
|
27
|
+
stats = writer.call(
|
|
28
|
+
file_path: "x.csv",
|
|
29
|
+
col_sep: ",",
|
|
30
|
+
headers: ["name", "city"],
|
|
31
|
+
start_row: 2,
|
|
32
|
+
end_row: 3,
|
|
33
|
+
output_path: output_path
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
assert_equal "name,city\nBob,Paris\nCara,Berlin\n", File.read(output_path)
|
|
37
|
+
assert_equal true, stats[:matched]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -18,7 +18,12 @@ class ErrorsPresenterTest < Minitest::Test
|
|
|
18
18
|
presenter.invalid_output_destination
|
|
19
19
|
presenter.empty_custom_separator
|
|
20
20
|
presenter.invalid_separator_choice
|
|
21
|
+
presenter.invalid_seed
|
|
21
22
|
presenter.canceled
|
|
23
|
+
presenter.invalid_start_row
|
|
24
|
+
presenter.invalid_end_row
|
|
25
|
+
presenter.invalid_row_range_order
|
|
26
|
+
presenter.row_range_out_of_bounds(3)
|
|
22
27
|
|
|
23
28
|
text = out.string
|
|
24
29
|
assert_includes text, "File not found: /tmp/x.csv"
|
|
@@ -31,6 +36,11 @@ class ErrorsPresenterTest < Minitest::Test
|
|
|
31
36
|
assert_includes text, "Invalid output destination."
|
|
32
37
|
assert_includes text, "Separator cannot be empty."
|
|
33
38
|
assert_includes text, "Invalid separator choice."
|
|
39
|
+
assert_includes text, "Seed must be an integer."
|
|
34
40
|
assert_includes text, "Canceled."
|
|
41
|
+
assert_includes text, "Start row must be a positive integer."
|
|
42
|
+
assert_includes text, "End row must be a positive integer."
|
|
43
|
+
assert_includes text, "End row must be greater than or equal to start row."
|
|
44
|
+
assert_includes text, "Row range is out of bounds. File has 3 data rows."
|
|
35
45
|
end
|
|
36
46
|
end
|
|
@@ -16,36 +16,92 @@ class MenuLoopTest < Minitest::Test
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
|
|
19
|
+
def test_routes_extract_column_then_exit
|
|
20
|
+
column_action = FakeAction.new
|
|
21
|
+
rows_action = FakeAction.new
|
|
22
|
+
randomize_rows_action = FakeAction.new
|
|
21
23
|
stdout = StringIO.new
|
|
22
24
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
23
|
-
stdin: StringIO.new("1\
|
|
25
|
+
stdin: StringIO.new("1\n4\n"),
|
|
24
26
|
stdout: stdout,
|
|
25
|
-
menu_options: ["Extract column", "Exit"],
|
|
26
|
-
|
|
27
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
28
|
+
extract_column_action: column_action,
|
|
29
|
+
extract_rows_action: rows_action,
|
|
30
|
+
randomize_rows_action: randomize_rows_action
|
|
27
31
|
)
|
|
28
32
|
|
|
29
33
|
status = menu.run
|
|
30
34
|
|
|
31
35
|
assert_equal 0, status
|
|
32
|
-
assert_equal 1,
|
|
36
|
+
assert_equal 1, column_action.runs
|
|
37
|
+
assert_equal 0, rows_action.runs
|
|
38
|
+
assert_equal 0, randomize_rows_action.runs
|
|
33
39
|
assert_includes stdout.string, "CSV Tool Menu"
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
def test_routes_extract_rows_then_exit
|
|
43
|
+
column_action = FakeAction.new
|
|
44
|
+
rows_action = FakeAction.new
|
|
45
|
+
randomize_rows_action = FakeAction.new
|
|
46
|
+
stdout = StringIO.new
|
|
47
|
+
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
48
|
+
stdin: StringIO.new("2\n4\n"),
|
|
49
|
+
stdout: stdout,
|
|
50
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
51
|
+
extract_column_action: column_action,
|
|
52
|
+
extract_rows_action: rows_action,
|
|
53
|
+
randomize_rows_action: randomize_rows_action
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
status = menu.run
|
|
57
|
+
|
|
58
|
+
assert_equal 0, status
|
|
59
|
+
assert_equal 0, column_action.runs
|
|
60
|
+
assert_equal 1, rows_action.runs
|
|
61
|
+
assert_equal 0, randomize_rows_action.runs
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_routes_randomize_rows_then_exit
|
|
65
|
+
column_action = FakeAction.new
|
|
66
|
+
rows_action = FakeAction.new
|
|
67
|
+
randomize_rows_action = FakeAction.new
|
|
68
|
+
stdout = StringIO.new
|
|
69
|
+
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
70
|
+
stdin: StringIO.new("3\n4\n"),
|
|
71
|
+
stdout: stdout,
|
|
72
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
73
|
+
extract_column_action: column_action,
|
|
74
|
+
extract_rows_action: rows_action,
|
|
75
|
+
randomize_rows_action: randomize_rows_action
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
status = menu.run
|
|
79
|
+
|
|
80
|
+
assert_equal 0, status
|
|
81
|
+
assert_equal 0, column_action.runs
|
|
82
|
+
assert_equal 0, rows_action.runs
|
|
83
|
+
assert_equal 1, randomize_rows_action.runs
|
|
84
|
+
end
|
|
85
|
+
|
|
36
86
|
def test_invalid_choice_shows_prompt
|
|
37
|
-
|
|
87
|
+
column_action = FakeAction.new
|
|
88
|
+
rows_action = FakeAction.new
|
|
89
|
+
randomize_rows_action = FakeAction.new
|
|
38
90
|
stdout = StringIO.new
|
|
39
91
|
menu = Csvtool::Interface::CLI::MenuLoop.new(
|
|
40
|
-
stdin: StringIO.new("x\
|
|
92
|
+
stdin: StringIO.new("x\n4\n"),
|
|
41
93
|
stdout: stdout,
|
|
42
|
-
menu_options: ["Extract column", "Exit"],
|
|
43
|
-
|
|
94
|
+
menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
|
|
95
|
+
extract_column_action: column_action,
|
|
96
|
+
extract_rows_action: rows_action,
|
|
97
|
+
randomize_rows_action: randomize_rows_action
|
|
44
98
|
)
|
|
45
99
|
|
|
46
100
|
menu.run
|
|
47
101
|
|
|
48
|
-
assert_includes stdout.string, "Please choose 1 or
|
|
49
|
-
assert_equal 0,
|
|
102
|
+
assert_includes stdout.string, "Please choose 1, 2, 3, or 4."
|
|
103
|
+
assert_equal 0, column_action.runs
|
|
104
|
+
assert_equal 0, rows_action.runs
|
|
105
|
+
assert_equal 0, randomize_rows_action.runs
|
|
50
106
|
end
|
|
51
107
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../../test_helper"
|
|
4
|
+
require "csvtool/interface/cli/prompts/headers_present_prompt"
|
|
5
|
+
|
|
6
|
+
class HeadersPresentPromptTest < Minitest::Test
|
|
7
|
+
def test_defaults_to_true_and_accepts_negative_inputs
|
|
8
|
+
yes_prompt = Csvtool::Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: StringIO.new("\n"), stdout: StringIO.new)
|
|
9
|
+
no_prompt = Csvtool::Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: StringIO.new("n\n"), stdout: StringIO.new)
|
|
10
|
+
|
|
11
|
+
assert_equal true, yes_prompt.call
|
|
12
|
+
assert_equal false, no_prompt.call
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../../test_helper"
|
|
4
|
+
require "csvtool/interface/cli/prompts/seed_prompt"
|
|
5
|
+
|
|
6
|
+
class SeedPromptTest < Minitest::Test
|
|
7
|
+
class FakeErrors
|
|
8
|
+
attr_reader :calls
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@calls = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def invalid_seed
|
|
15
|
+
@calls << :invalid_seed
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_blank_returns_nil
|
|
20
|
+
errors = FakeErrors.new
|
|
21
|
+
prompt = Csvtool::Interface::CLI::Prompts::SeedPrompt.new(stdin: StringIO.new("\n"), stdout: StringIO.new, errors: errors)
|
|
22
|
+
assert_nil prompt.call
|
|
23
|
+
assert_empty errors.calls
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def test_integer_returns_seed
|
|
27
|
+
errors = FakeErrors.new
|
|
28
|
+
prompt = Csvtool::Interface::CLI::Prompts::SeedPrompt.new(stdin: StringIO.new("42\n"), stdout: StringIO.new, errors: errors)
|
|
29
|
+
assert_equal 42, prompt.call
|
|
30
|
+
assert_empty errors.calls
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_invalid_reports_error
|
|
34
|
+
errors = FakeErrors.new
|
|
35
|
+
prompt = Csvtool::Interface::CLI::Prompts::SeedPrompt.new(stdin: StringIO.new("abc\n"), stdout: StringIO.new, errors: errors)
|
|
36
|
+
assert_equal Csvtool::Interface::CLI::Prompts::SeedPrompt::INVALID, prompt.call
|
|
37
|
+
assert_includes errors.calls, :invalid_seed
|
|
38
|
+
end
|
|
39
|
+
end
|