csvops 0.1.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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/README.md +209 -0
  4. data/Rakefile +10 -0
  5. data/bin/csvtool +8 -0
  6. data/bin/tool +8 -0
  7. data/csvops.gemspec +25 -0
  8. data/docs/release-v0.1.0-alpha.md +73 -0
  9. data/exe/csvtool +8 -0
  10. data/lib/csvtool/application/use_cases/run_extraction.rb +125 -0
  11. data/lib/csvtool/cli.rb +87 -0
  12. data/lib/csvtool/domain/extraction_session/column_selection.rb +17 -0
  13. data/lib/csvtool/domain/extraction_session/csv_source.rb +18 -0
  14. data/lib/csvtool/domain/extraction_session/extraction_options.rb +22 -0
  15. data/lib/csvtool/domain/extraction_session/extraction_session.rb +61 -0
  16. data/lib/csvtool/domain/extraction_session/extraction_value.rb +15 -0
  17. data/lib/csvtool/domain/extraction_session/output_destination.rb +35 -0
  18. data/lib/csvtool/domain/extraction_session/preview.rb +23 -0
  19. data/lib/csvtool/domain/extraction_session/separator.rb +17 -0
  20. data/lib/csvtool/infrastructure/csv/header_reader.rb +17 -0
  21. data/lib/csvtool/infrastructure/csv/value_streamer.rb +20 -0
  22. data/lib/csvtool/infrastructure/output/console_writer.rb +20 -0
  23. data/lib/csvtool/infrastructure/output/csv_file_writer.rb +30 -0
  24. data/lib/csvtool/interface/cli/errors/presenter.rb +59 -0
  25. data/lib/csvtool/interface/cli/menu_loop.rb +41 -0
  26. data/lib/csvtool/interface/cli/prompts/column_selector_prompt.rb +54 -0
  27. data/lib/csvtool/interface/cli/prompts/confirm_prompt.rb +29 -0
  28. data/lib/csvtool/interface/cli/prompts/file_path_prompt.rb +21 -0
  29. data/lib/csvtool/interface/cli/prompts/output_destination_prompt.rb +40 -0
  30. data/lib/csvtool/interface/cli/prompts/separator_prompt.rb +44 -0
  31. data/lib/csvtool/interface/cli/prompts/skip_blanks_prompt.rb +22 -0
  32. data/lib/csvtool/services/preview_builder.rb +20 -0
  33. data/lib/csvtool/version.rb +5 -0
  34. data/test/csvtool/application/use_cases/run_extraction_test.rb +31 -0
  35. data/test/csvtool/cli_test.rb +134 -0
  36. data/test/csvtool/cli_unit_test.rb +27 -0
  37. data/test/csvtool/domain/extraction_session/column_selection_test.rb +11 -0
  38. data/test/csvtool/domain/extraction_session/csv_source_test.rb +14 -0
  39. data/test/csvtool/domain/extraction_session/extraction_options_test.rb +18 -0
  40. data/test/csvtool/domain/extraction_session/extraction_session_test.rb +35 -0
  41. data/test/csvtool/domain/extraction_session/extraction_value_test.rb +11 -0
  42. data/test/csvtool/domain/extraction_session/output_destination_test.rb +18 -0
  43. data/test/csvtool/domain/extraction_session/preview_test.rb +18 -0
  44. data/test/csvtool/domain/extraction_session/separator_test.rb +15 -0
  45. data/test/csvtool/infrastructure/csv/header_reader_test.rb +16 -0
  46. data/test/csvtool/infrastructure/csv/value_streamer_test.rb +22 -0
  47. data/test/csvtool/infrastructure/output/console_writer_test.rb +19 -0
  48. data/test/csvtool/infrastructure/output/csv_file_writer_test.rb +35 -0
  49. data/test/csvtool/interface/cli/errors/presenter_test.rb +36 -0
  50. data/test/csvtool/interface/cli/menu_loop_test.rb +51 -0
  51. data/test/csvtool/interface/cli/prompts/column_selector_prompt_test.rb +23 -0
  52. data/test/csvtool/interface/cli/prompts/confirm_prompt_test.rb +23 -0
  53. data/test/csvtool/interface/cli/prompts/file_path_prompt_test.rb +11 -0
  54. data/test/csvtool/interface/cli/prompts/output_destination_prompt_test.rb +28 -0
  55. data/test/csvtool/interface/cli/prompts/separator_prompt_test.rb +31 -0
  56. data/test/csvtool/interface/cli/prompts/skip_blanks_prompt_test.rb +13 -0
  57. data/test/csvtool/services/preview_builder_test.rb +22 -0
  58. data/test/fixtures/empty.csv +0 -0
  59. data/test/fixtures/sample_people.csv +4 -0
  60. data/test/fixtures/sample_people.tsv +4 -0
  61. data/test/fixtures/sample_people_blanks.csv +6 -0
  62. data/test/fixtures/sample_people_colon.txt +4 -0
  63. data/test/fixtures/sample_people_many.csv +13 -0
  64. data/test/test_helper.rb +6 -0
  65. metadata +150 -0
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+ require "csvtool/cli"
5
+ require "tmpdir"
6
+
7
+ class TestCli < Minitest::Test
8
+ def fixture_path(name)
9
+ File.expand_path("../fixtures/#{name}", __dir__)
10
+ end
11
+
12
+ def test_menu_can_exit_cleanly
13
+ output = StringIO.new
14
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("2\n"), stdout: output, stderr: StringIO.new)
15
+ assert_equal 0, status
16
+ assert_includes output.string, "CSV Tool Menu"
17
+ end
18
+
19
+ def test_end_to_end_console_happy_path_prints_expected_values
20
+ input = [
21
+ "1",
22
+ fixture_path("sample_people.csv"),
23
+ "1",
24
+ "",
25
+ "1",
26
+ "",
27
+ "y",
28
+ "",
29
+ "2"
30
+ ].join("\n") + "\n"
31
+
32
+ output = StringIO.new
33
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
34
+
35
+ assert_equal 0, status
36
+ assert_match(/\nAlice\nBob\nCara\n/, output.string)
37
+ end
38
+
39
+ def test_column_command_prints_expected_values
40
+ output = StringIO.new
41
+ status = Csvtool::CLI.start(
42
+ ["column", fixture_path("sample_people.csv"), "name"],
43
+ stdin: StringIO.new,
44
+ stdout: output,
45
+ stderr: StringIO.new
46
+ )
47
+
48
+ assert_equal 0, status
49
+ assert_equal "Alice\nBob\nCara\n", output.string
50
+ end
51
+
52
+ def test_end_to_end_file_output_writes_expected_csv
53
+ output = StringIO.new
54
+ output_path = nil
55
+
56
+ Dir.mktmpdir do |dir|
57
+ output_path = File.join(dir, "names.csv")
58
+ input = [
59
+ "1",
60
+ fixture_path("sample_people.csv"),
61
+ "1",
62
+ "",
63
+ "1",
64
+ "",
65
+ "y",
66
+ "2",
67
+ output_path,
68
+ "2"
69
+ ].join("\n") + "\n"
70
+
71
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
72
+ assert_equal 0, status
73
+ assert_equal "name\nAlice\nBob\nCara\n", File.read(output_path)
74
+ end
75
+
76
+ assert_includes output.string, "Wrote output to #{output_path}"
77
+ end
78
+
79
+ def test_preview_cancel_returns_to_menu
80
+ input = [
81
+ "1",
82
+ fixture_path("sample_people_many.csv"),
83
+ "1",
84
+ "",
85
+ "1",
86
+ "",
87
+ "n",
88
+ "2"
89
+ ].join("\n") + "\n"
90
+
91
+ output = StringIO.new
92
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
93
+
94
+ assert_equal 0, status
95
+ assert_includes output.string, "Canceled."
96
+ assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
97
+ end
98
+
99
+ def test_missing_file_shows_error_and_returns_to_menu
100
+ output = StringIO.new
101
+ status = Csvtool::CLI.start(
102
+ ["menu"],
103
+ stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n2\n"),
104
+ stdout: output,
105
+ stderr: StringIO.new
106
+ )
107
+
108
+ assert_equal 0, status
109
+ assert_includes output.string, "File not found: /tmp/does-not-exist.csv"
110
+ assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
111
+ end
112
+
113
+ def test_invalid_output_path_shows_error_and_returns_to_menu
114
+ input = [
115
+ "1",
116
+ fixture_path("sample_people.csv"),
117
+ "1",
118
+ "",
119
+ "1",
120
+ "",
121
+ "y",
122
+ "2",
123
+ "/tmp/not-a-dir/out.csv",
124
+ "2"
125
+ ].join("\n") + "\n"
126
+
127
+ output = StringIO.new
128
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
129
+
130
+ assert_equal 0, status
131
+ assert_includes output.string, "Cannot write output file: /tmp/not-a-dir/out.csv"
132
+ assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
133
+ end
134
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../test_helper"
4
+ require "csvtool/cli"
5
+
6
+ class CliUnitTest < Minitest::Test
7
+ def test_unknown_command_prints_usage_and_returns_one
8
+ stdout = StringIO.new
9
+ stderr = StringIO.new
10
+
11
+ status = Csvtool::CLI.start(["unknown"], stdin: StringIO.new, stdout: stdout, stderr: stderr)
12
+
13
+ assert_equal 1, status
14
+ assert_includes stderr.string, "csvtool menu"
15
+ assert_includes stderr.string, "csvtool column <file> <column>"
16
+ end
17
+
18
+ def test_menu_command_can_exit_zero
19
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("2\n"), stdout: StringIO.new, stderr: StringIO.new)
20
+ assert_equal 0, status
21
+ end
22
+
23
+ def test_column_command_requires_file_and_column_args
24
+ status = Csvtool::CLI.start(["column"], stdin: StringIO.new, stdout: StringIO.new, stderr: StringIO.new)
25
+ assert_equal 1, status
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/column_selection"
5
+
6
+ class ColumnSelectionTest < Minitest::Test
7
+ def test_stores_name
8
+ selection = Csvtool::Domain::ExtractionSession::ColumnSelection.new(name: "city")
9
+ assert_equal "city", selection.name
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/csv_source"
5
+ require "csvtool/domain/extraction_session/separator"
6
+
7
+ class CsvSourceTest < Minitest::Test
8
+ def test_stores_path_and_separator
9
+ separator = Csvtool::Domain::ExtractionSession::Separator.new(",")
10
+ source = Csvtool::Domain::ExtractionSession::CsvSource.new(path: "/tmp/a.csv", separator: separator)
11
+ assert_equal "/tmp/a.csv", source.path
12
+ assert_equal separator, source.separator
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/extraction_options"
5
+
6
+ class ExtractionOptionsTest < Minitest::Test
7
+ def test_exposes_options
8
+ options = Csvtool::Domain::ExtractionSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 10)
9
+ assert_equal true, options.skip_blanks?
10
+ assert_equal 10, options.preview_limit
11
+ end
12
+
13
+ def test_non_positive_preview_limit_raises
14
+ assert_raises(ArgumentError) do
15
+ Csvtool::Domain::ExtractionSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 0)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/extraction_session"
5
+ require "csvtool/domain/extraction_session/csv_source"
6
+ require "csvtool/domain/extraction_session/separator"
7
+ require "csvtool/domain/extraction_session/column_selection"
8
+ require "csvtool/domain/extraction_session/extraction_options"
9
+ require "csvtool/domain/extraction_session/preview"
10
+ require "csvtool/domain/extraction_session/extraction_value"
11
+ require "csvtool/domain/extraction_session/output_destination"
12
+
13
+ class ExtractionSessionTest < Minitest::Test
14
+ def test_state_transitions
15
+ session = Csvtool::Domain::ExtractionSession::ExtractionSession.start(
16
+ source: Csvtool::Domain::ExtractionSession::CsvSource.new(
17
+ path: "/tmp/in.csv",
18
+ separator: Csvtool::Domain::ExtractionSession::Separator.new(",")
19
+ ),
20
+ column_selection: Csvtool::Domain::ExtractionSession::ColumnSelection.new(name: "name"),
21
+ options: Csvtool::Domain::ExtractionSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 10)
22
+ )
23
+
24
+ preview = Csvtool::Domain::ExtractionSession::Preview.new(
25
+ values: [Csvtool::Domain::ExtractionSession::ExtractionValue.new("Alice")]
26
+ )
27
+ session = session.with_preview(preview).confirm!.with_output_destination(
28
+ Csvtool::Domain::ExtractionSession::OutputDestination.console
29
+ )
30
+
31
+ assert_equal true, session.confirmed?
32
+ assert_equal preview, session.preview
33
+ assert_equal true, session.output_destination.console?
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/extraction_value"
5
+
6
+ class ExtractionValueTest < Minitest::Test
7
+ def test_stringifies_value
8
+ value = Csvtool::Domain::ExtractionSession::ExtractionValue.new(123)
9
+ assert_equal "123", value.value
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/output_destination"
5
+
6
+ class OutputDestinationTest < Minitest::Test
7
+ def test_console_factory
8
+ destination = Csvtool::Domain::ExtractionSession::OutputDestination.console
9
+ assert_equal true, destination.console?
10
+ assert_equal false, destination.file?
11
+ end
12
+
13
+ def test_file_factory
14
+ destination = Csvtool::Domain::ExtractionSession::OutputDestination.file(path: "/tmp/out.csv")
15
+ assert_equal true, destination.file?
16
+ assert_equal "/tmp/out.csv", destination.path
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/preview"
5
+ require "csvtool/domain/extraction_session/extraction_value"
6
+
7
+ class PreviewTest < Minitest::Test
8
+ def test_exposes_size_and_string_values
9
+ values = [
10
+ Csvtool::Domain::ExtractionSession::ExtractionValue.new("Alice"),
11
+ Csvtool::Domain::ExtractionSession::ExtractionValue.new("Bob")
12
+ ]
13
+ preview = Csvtool::Domain::ExtractionSession::Preview.new(values: values)
14
+
15
+ assert_equal 2, preview.size
16
+ assert_equal %w[Alice Bob], preview.to_strings
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/extraction_session/separator"
5
+
6
+ class SeparatorTest < Minitest::Test
7
+ def test_stores_value
8
+ separator = Csvtool::Domain::ExtractionSession::Separator.new(",")
9
+ assert_equal ",", separator.value
10
+ end
11
+
12
+ def test_empty_value_raises
13
+ assert_raises(ArgumentError) { Csvtool::Domain::ExtractionSession::Separator.new("") }
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/infrastructure/csv/header_reader"
5
+
6
+ class InfrastructureHeaderReaderTest < Minitest::Test
7
+ def fixture_path(name)
8
+ File.expand_path("../../../fixtures/#{name}", __dir__)
9
+ end
10
+
11
+ def test_returns_headers
12
+ reader = Csvtool::Infrastructure::CSV::HeaderReader.new
13
+ headers = reader.call(file_path: fixture_path("sample_people.csv"), col_sep: ",")
14
+ assert_equal %w[name city], headers
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/infrastructure/csv/value_streamer"
5
+
6
+ class InfrastructureValueStreamerTest < Minitest::Test
7
+ def fixture_path(name)
8
+ File.expand_path("../../../fixtures/#{name}", __dir__)
9
+ end
10
+
11
+ def test_filters_blanks_when_enabled
12
+ streamer = Csvtool::Infrastructure::CSV::ValueStreamer.new
13
+ values = []
14
+ streamer.each(
15
+ file_path: fixture_path("sample_people_blanks.csv"),
16
+ column_name: "name",
17
+ col_sep: ",",
18
+ skip_blanks: true
19
+ ) { |v| values << v }
20
+ assert_equal %w[Alice Bob Cara], values
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/infrastructure/output/console_writer"
5
+
6
+ class InfrastructureConsoleWriterTest < Minitest::Test
7
+ class FakeStreamer
8
+ def each(file_path:, column_name:, col_sep:, skip_blanks:)
9
+ %w[Alice Bob].each { |value| yield value }
10
+ end
11
+ end
12
+
13
+ def test_writes_streamed_values_to_stdout
14
+ out = StringIO.new
15
+ writer = Csvtool::Infrastructure::Output::ConsoleWriter.new(stdout: out, value_streamer: FakeStreamer.new)
16
+ writer.call(file_path: "x.csv", column_name: "name", col_sep: ",", skip_blanks: true)
17
+ assert_equal "Alice\nBob\n", out.string
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/infrastructure/output/csv_file_writer"
5
+ require "csvtool/interface/cli/errors/presenter"
6
+ require "tmpdir"
7
+
8
+ class InfrastructureCsvFileWriterTest < Minitest::Test
9
+ class FakeStreamer
10
+ def each(file_path:, column_name:, col_sep:, skip_blanks:)
11
+ %w[Alice Bob].each { |value| yield value }
12
+ end
13
+ end
14
+
15
+ def test_writes_header_and_values
16
+ stdout = StringIO.new
17
+ writer = Csvtool::Infrastructure::Output::CsvFileWriter.new(
18
+ stdout: stdout,
19
+ errors: Csvtool::Interface::CLI::Errors::Presenter.new(stdout: stdout),
20
+ value_streamer: FakeStreamer.new
21
+ )
22
+
23
+ Dir.mktmpdir do |dir|
24
+ output_path = File.join(dir, "names.csv")
25
+ writer.call(
26
+ file_path: "ignored.csv",
27
+ column_name: "name",
28
+ col_sep: ",",
29
+ skip_blanks: true,
30
+ output_path: output_path
31
+ )
32
+ assert_equal "name\nAlice\nBob\n", File.read(output_path)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/errors/presenter"
5
+
6
+ class ErrorsPresenterTest < Minitest::Test
7
+ def test_all_messages_are_presented
8
+ out = StringIO.new
9
+ presenter = Csvtool::Interface::CLI::Errors::Presenter.new(stdout: out)
10
+
11
+ presenter.file_not_found("/tmp/x.csv")
12
+ presenter.no_headers
13
+ presenter.column_not_found
14
+ presenter.could_not_parse_csv
15
+ presenter.cannot_read_file("/tmp/y.csv")
16
+ presenter.cannot_write_output_file("/tmp/z.csv", Errno::ENOENT)
17
+ presenter.empty_output_path
18
+ presenter.invalid_output_destination
19
+ presenter.empty_custom_separator
20
+ presenter.invalid_separator_choice
21
+ presenter.canceled
22
+
23
+ text = out.string
24
+ assert_includes text, "File not found: /tmp/x.csv"
25
+ assert_includes text, "No headers found."
26
+ assert_includes text, "Column not found."
27
+ assert_includes text, "Could not parse CSV file."
28
+ assert_includes text, "Cannot read file: /tmp/y.csv"
29
+ assert_includes text, "Cannot write output file: /tmp/z.csv (Errno::ENOENT)"
30
+ assert_includes text, "Output file path cannot be empty."
31
+ assert_includes text, "Invalid output destination."
32
+ assert_includes text, "Separator cannot be empty."
33
+ assert_includes text, "Invalid separator choice."
34
+ assert_includes text, "Canceled."
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/interface/cli/menu_loop"
5
+
6
+ class MenuLoopTest < Minitest::Test
7
+ class FakeAction
8
+ attr_reader :runs
9
+
10
+ def initialize
11
+ @runs = 0
12
+ end
13
+
14
+ def call
15
+ @runs += 1
16
+ end
17
+ end
18
+
19
+ def test_routes_extract_then_exit
20
+ action = FakeAction.new
21
+ stdout = StringIO.new
22
+ menu = Csvtool::Interface::CLI::MenuLoop.new(
23
+ stdin: StringIO.new("1\n2\n"),
24
+ stdout: stdout,
25
+ menu_options: ["Extract column", "Exit"],
26
+ extract_action: action
27
+ )
28
+
29
+ status = menu.run
30
+
31
+ assert_equal 0, status
32
+ assert_equal 1, action.runs
33
+ assert_includes stdout.string, "CSV Tool Menu"
34
+ end
35
+
36
+ def test_invalid_choice_shows_prompt
37
+ action = FakeAction.new
38
+ stdout = StringIO.new
39
+ menu = Csvtool::Interface::CLI::MenuLoop.new(
40
+ stdin: StringIO.new("x\n2\n"),
41
+ stdout: stdout,
42
+ menu_options: ["Extract column", "Exit"],
43
+ extract_action: action
44
+ )
45
+
46
+ menu.run
47
+
48
+ assert_includes stdout.string, "Please choose 1 or 2."
49
+ assert_equal 0, action.runs
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/column_selector_prompt"
5
+
6
+ class ColumnSelectorPromptTest < Minitest::Test
7
+ class FakeErrors
8
+ attr_reader :calls
9
+
10
+ def initialize
11
+ @calls = []
12
+ end
13
+
14
+ def column_not_found = @calls << :column_not_found
15
+ end
16
+
17
+ def test_selects_filtered_header
18
+ errors = FakeErrors.new
19
+ prompt = Csvtool::Interface::CLI::Prompts::ColumnSelectorPrompt.new(stdin: StringIO.new("ci\n1\n"), stdout: StringIO.new, errors: errors)
20
+ assert_equal "city", prompt.call(%w[name city])
21
+ assert_empty errors.calls
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/confirm_prompt"
5
+
6
+ class ConfirmPromptTest < Minitest::Test
7
+ class FakeErrors
8
+ attr_reader :calls
9
+
10
+ def initialize
11
+ @calls = []
12
+ end
13
+
14
+ def canceled = @calls << :canceled
15
+ end
16
+
17
+ def test_calls_canceled_on_no
18
+ errors = FakeErrors.new
19
+ prompt = Csvtool::Interface::CLI::Prompts::ConfirmPrompt.new(stdin: StringIO.new("n\n"), stdout: StringIO.new, errors: errors)
20
+ assert_equal false, prompt.call(%w[a b])
21
+ assert_includes errors.calls, :canceled
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/file_path_prompt"
5
+
6
+ class FilePathPromptTest < Minitest::Test
7
+ def test_returns_trimmed_path
8
+ prompt = Csvtool::Interface::CLI::Prompts::FilePathPrompt.new(stdin: StringIO.new(" /tmp/a.csv \n"), stdout: StringIO.new)
9
+ assert_equal "/tmp/a.csv", prompt.call
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/output_destination_prompt"
5
+ require "csvtool/interface/cli/errors/presenter"
6
+
7
+ class OutputDestinationPromptTest < Minitest::Test
8
+ def test_defaults_to_console
9
+ stdout = StringIO.new
10
+ prompt = Csvtool::Interface::CLI::Prompts::OutputDestinationPrompt.new(
11
+ stdin: StringIO.new("\n"),
12
+ stdout: stdout,
13
+ errors: Csvtool::Interface::CLI::Errors::Presenter.new(stdout: stdout)
14
+ )
15
+ assert_equal({ mode: :console }, prompt.call)
16
+ end
17
+
18
+ def test_file_mode_requires_path
19
+ stdout = StringIO.new
20
+ prompt = Csvtool::Interface::CLI::Prompts::OutputDestinationPrompt.new(
21
+ stdin: StringIO.new("2\n\n"),
22
+ stdout: stdout,
23
+ errors: Csvtool::Interface::CLI::Errors::Presenter.new(stdout: stdout)
24
+ )
25
+ assert_nil prompt.call
26
+ assert_includes stdout.string, "Output file path cannot be empty."
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/separator_prompt"
5
+
6
+ class SeparatorPromptTest < Minitest::Test
7
+ class FakeErrors
8
+ attr_reader :calls
9
+
10
+ def initialize
11
+ @calls = []
12
+ end
13
+
14
+ def empty_custom_separator = @calls << :empty_custom_separator
15
+ def invalid_separator_choice = @calls << :invalid_separator_choice
16
+ end
17
+
18
+ def test_defaults_to_comma
19
+ errors = FakeErrors.new
20
+ prompt = Csvtool::Interface::CLI::Prompts::SeparatorPrompt.new(stdin: StringIO.new("\n"), stdout: StringIO.new, errors: errors)
21
+ assert_equal ",", prompt.call
22
+ assert_empty errors.calls
23
+ end
24
+
25
+ def test_custom_empty_reports_error
26
+ errors = FakeErrors.new
27
+ prompt = Csvtool::Interface::CLI::Prompts::SeparatorPrompt.new(stdin: StringIO.new("5\n\n"), stdout: StringIO.new, errors: errors)
28
+ assert_nil prompt.call
29
+ assert_includes errors.calls, :empty_custom_separator
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/skip_blanks_prompt"
5
+
6
+ class SkipBlanksPromptTest < Minitest::Test
7
+ def test_default_true_and_no_false
8
+ prompt_yes = Csvtool::Interface::CLI::Prompts::SkipBlanksPrompt.new(stdin: StringIO.new("\n"), stdout: StringIO.new)
9
+ prompt_no = Csvtool::Interface::CLI::Prompts::SkipBlanksPrompt.new(stdin: StringIO.new("n\n"), stdout: StringIO.new)
10
+ assert_equal true, prompt_yes.call
11
+ assert_equal false, prompt_no.call
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../test_helper"
4
+ require "csvtool/services/preview_builder"
5
+
6
+ class PreviewBuilderTest < Minitest::Test
7
+ class FakeStreamer
8
+ def initialize(values)
9
+ @values = values
10
+ end
11
+
12
+ def each(file_path:, column_name:, col_sep:, skip_blanks:)
13
+ @values.each { |value| yield value }
14
+ end
15
+ end
16
+
17
+ def test_builds_preview_up_to_limit
18
+ builder = Csvtool::Services::PreviewBuilder.new(value_streamer: FakeStreamer.new(%w[a b c d]))
19
+ preview = builder.call(file_path: "x.csv", column_name: "name", col_sep: ",", skip_blanks: true, limit: 2)
20
+ assert_equal %w[a b], preview
21
+ end
22
+ end
File without changes
@@ -0,0 +1,4 @@
1
+ name,city
2
+ Alice,London
3
+ Bob,Paris
4
+ Cara,Berlin
@@ -0,0 +1,4 @@
1
+ name city
2
+ Alice London
3
+ Bob Paris
4
+ Cara Berlin