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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +83 -10
  3. data/docs/release-v0.2.0-alpha.md +80 -0
  4. data/docs/release-v0.3.0-alpha.md +74 -0
  5. data/lib/csvtool/application/use_cases/run_extraction.rb +17 -17
  6. data/lib/csvtool/application/use_cases/run_row_extraction.rb +111 -0
  7. data/lib/csvtool/application/use_cases/run_row_randomization.rb +105 -0
  8. data/lib/csvtool/cli.rb +10 -2
  9. data/lib/csvtool/domain/{extraction_session → column_session}/column_selection.rb +1 -1
  10. data/lib/csvtool/domain/{extraction_session/extraction_session.rb → column_session/column_session.rb} +2 -2
  11. data/lib/csvtool/domain/{extraction_session → column_session}/csv_source.rb +1 -1
  12. data/lib/csvtool/domain/{extraction_session → column_session}/extraction_options.rb +1 -1
  13. data/lib/csvtool/domain/{extraction_session → column_session}/extraction_value.rb +1 -1
  14. data/lib/csvtool/domain/{extraction_session → column_session}/output_destination.rb +1 -1
  15. data/lib/csvtool/domain/{extraction_session → column_session}/preview.rb +1 -1
  16. data/lib/csvtool/domain/{extraction_session → column_session}/separator.rb +1 -1
  17. data/lib/csvtool/domain/row_randomization_session/randomization_options.rb +17 -0
  18. data/lib/csvtool/domain/row_randomization_session/randomization_output_destination.rb +31 -0
  19. data/lib/csvtool/domain/row_randomization_session/randomization_session.rb +25 -0
  20. data/lib/csvtool/domain/row_randomization_session/randomization_source.rb +23 -0
  21. data/lib/csvtool/domain/row_session/row_output_destination.rb +31 -0
  22. data/lib/csvtool/domain/row_session/row_range.rb +39 -0
  23. data/lib/csvtool/domain/row_session/row_session.rb +25 -0
  24. data/lib/csvtool/domain/row_session/row_source.rb +16 -0
  25. data/lib/csvtool/infrastructure/csv/row_randomizer.rb +83 -0
  26. data/lib/csvtool/infrastructure/csv/row_streamer.rb +27 -0
  27. data/lib/csvtool/infrastructure/output/csv_row_console_writer.rb +34 -0
  28. data/lib/csvtool/infrastructure/output/csv_row_file_writer.rb +45 -0
  29. data/lib/csvtool/interface/cli/errors/presenter.rb +20 -0
  30. data/lib/csvtool/interface/cli/menu_loop.rb +13 -5
  31. data/lib/csvtool/interface/cli/prompts/headers_present_prompt.rb +22 -0
  32. data/lib/csvtool/interface/cli/prompts/seed_prompt.rb +29 -0
  33. data/lib/csvtool/version.rb +1 -1
  34. data/test/csvtool/application/use_cases/run_row_extraction_test.rb +140 -0
  35. data/test/csvtool/application/use_cases/run_row_randomization_test.rb +124 -0
  36. data/test/csvtool/cli_test.rb +237 -6
  37. data/test/csvtool/cli_unit_test.rb +24 -1
  38. data/test/csvtool/domain/{extraction_session → column_session}/column_selection_test.rb +2 -2
  39. data/test/csvtool/domain/column_session/column_session_test.rb +35 -0
  40. data/test/csvtool/domain/column_session/csv_source_test.rb +14 -0
  41. data/test/csvtool/domain/{extraction_session → column_session}/extraction_options_test.rb +3 -3
  42. data/test/csvtool/domain/{extraction_session → column_session}/extraction_value_test.rb +2 -2
  43. data/test/csvtool/domain/{extraction_session → column_session}/output_destination_test.rb +3 -3
  44. data/test/csvtool/domain/column_session/preview_test.rb +18 -0
  45. data/test/csvtool/domain/{extraction_session → column_session}/separator_test.rb +3 -3
  46. data/test/csvtool/domain/row_randomization_session/randomization_options_test.rb +20 -0
  47. data/test/csvtool/domain/row_randomization_session/randomization_output_destination_test.rb +21 -0
  48. data/test/csvtool/domain/row_randomization_session/randomization_session_test.rb +26 -0
  49. data/test/csvtool/domain/row_randomization_session/randomization_source_test.rb +28 -0
  50. data/test/csvtool/domain/row_session/row_output_destination_test.rb +23 -0
  51. data/test/csvtool/domain/row_session/row_range_test.rb +30 -0
  52. data/test/csvtool/domain/row_session/row_session_test.rb +22 -0
  53. data/test/csvtool/domain/row_session/row_source_test.rb +12 -0
  54. data/test/csvtool/infrastructure/csv/row_randomizer_test.rb +37 -0
  55. data/test/csvtool/infrastructure/csv/row_streamer_test.rb +41 -0
  56. data/test/csvtool/infrastructure/output/csv_row_console_writer_test.rb +24 -0
  57. data/test/csvtool/infrastructure/output/csv_row_file_writer_test.rb +40 -0
  58. data/test/csvtool/interface/cli/errors/presenter_test.rb +10 -0
  59. data/test/csvtool/interface/cli/menu_loop_test.rb +68 -12
  60. data/test/csvtool/interface/cli/prompts/headers_present_prompt_test.rb +14 -0
  61. data/test/csvtool/interface/cli/prompts/seed_prompt_test.rb +39 -0
  62. data/test/fixtures/sample_people_bad_tail.csv +5 -0
  63. data/test/fixtures/sample_people_no_headers.csv +3 -0
  64. metadata +53 -17
  65. data/test/csvtool/domain/extraction_session/csv_source_test.rb +0 -14
  66. data/test/csvtool/domain/extraction_session/extraction_session_test.rb +0 -35
  67. 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/extraction_session/extraction_value"
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::ExtractionSession::ExtractionValue.new(123)
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/extraction_session/output_destination"
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::ExtractionSession::OutputDestination.console
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::ExtractionSession::OutputDestination.file(path: "/tmp/out.csv")
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/extraction_session/separator"
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::ExtractionSession::Separator.new(",")
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::ExtractionSession::Separator.new("") }
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 test_routes_extract_then_exit
20
- action = FakeAction.new
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\n2\n"),
25
+ stdin: StringIO.new("1\n4\n"),
24
26
  stdout: stdout,
25
- menu_options: ["Extract column", "Exit"],
26
- extract_action: action
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, action.runs
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
- action = FakeAction.new
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\n2\n"),
92
+ stdin: StringIO.new("x\n4\n"),
41
93
  stdout: stdout,
42
- menu_options: ["Extract column", "Exit"],
43
- extract_action: action
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 2."
49
- assert_equal 0, action.runs
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
@@ -0,0 +1,5 @@
1
+ name,city
2
+ Alice,London
3
+ Bob,Paris
4
+ Cara,Berlin
5
+ "broken
@@ -0,0 +1,3 @@
1
+ Alice,London
2
+ Bob,Paris
3
+ Cara,Berlin