csvops 0.2.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -5
  3. data/docs/release-v0.3.0-alpha.md +74 -0
  4. data/lib/csvtool/application/use_cases/run_row_randomization.rb +105 -0
  5. data/lib/csvtool/cli.rb +5 -1
  6. data/lib/csvtool/domain/row_randomization_session/randomization_options.rb +17 -0
  7. data/lib/csvtool/domain/row_randomization_session/randomization_output_destination.rb +31 -0
  8. data/lib/csvtool/domain/row_randomization_session/randomization_session.rb +25 -0
  9. data/lib/csvtool/domain/row_randomization_session/randomization_source.rb +23 -0
  10. data/lib/csvtool/infrastructure/csv/row_randomizer.rb +83 -0
  11. data/lib/csvtool/interface/cli/errors/presenter.rb +4 -0
  12. data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
  13. data/lib/csvtool/interface/cli/prompts/headers_present_prompt.rb +22 -0
  14. data/lib/csvtool/interface/cli/prompts/seed_prompt.rb +29 -0
  15. data/lib/csvtool/version.rb +1 -1
  16. data/test/csvtool/application/use_cases/run_row_randomization_test.rb +124 -0
  17. data/test/csvtool/cli_test.rb +117 -12
  18. data/test/csvtool/cli_unit_test.rb +14 -2
  19. data/test/csvtool/domain/row_randomization_session/randomization_options_test.rb +20 -0
  20. data/test/csvtool/domain/row_randomization_session/randomization_output_destination_test.rb +21 -0
  21. data/test/csvtool/domain/row_randomization_session/randomization_session_test.rb +26 -0
  22. data/test/csvtool/domain/row_randomization_session/randomization_source_test.rb +28 -0
  23. data/test/csvtool/infrastructure/csv/row_randomizer_test.rb +37 -0
  24. data/test/csvtool/interface/cli/errors/presenter_test.rb +2 -0
  25. data/test/csvtool/interface/cli/menu_loop_test.rb +41 -10
  26. data/test/csvtool/interface/cli/prompts/headers_present_prompt_test.rb +14 -0
  27. data/test/csvtool/interface/cli/prompts/seed_prompt_test.rb +39 -0
  28. data/test/fixtures/sample_people_no_headers.csv +3 -0
  29. metadata +19 -1
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/application/use_cases/run_row_randomization"
5
+ require "tmpdir"
6
+
7
+ class RunRowRandomizationTest < Minitest::Test
8
+ def test_prints_header_then_all_randomized_rows
9
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
10
+ output = StringIO.new
11
+ input = StringIO.new("#{fixture}\n\n\n\n\n")
12
+
13
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
14
+
15
+ assert_includes output.string, "CSV file path:"
16
+ header_index = output.string.index("name,city")
17
+ assert header_index
18
+ %w[Alice,London Bob,Paris Cara,Berlin].each do |row|
19
+ row_index = output.string.index(row)
20
+ assert row_index
21
+ assert_operator header_index, :<, row_index
22
+ end
23
+ end
24
+
25
+ def test_missing_file_shows_friendly_error
26
+ output = StringIO.new
27
+ input = StringIO.new("/tmp/does-not-exist.csv\n")
28
+
29
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
30
+
31
+ assert_includes output.string, "File not found: /tmp/does-not-exist.csv"
32
+ end
33
+
34
+ def test_can_write_randomized_rows_to_file
35
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
36
+ output = StringIO.new
37
+
38
+ Dir.mktmpdir do |dir|
39
+ output_path = File.join(dir, "randomized.csv")
40
+ input = StringIO.new("#{fixture}\n\n\n\n2\n#{output_path}\n")
41
+
42
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
43
+
44
+ written = File.read(output_path).lines.map(&:strip)
45
+ assert_equal "name,city", written.first
46
+ assert_equal ["Alice,London", "Bob,Paris", "Cara,Berlin"].sort, written[1..].sort
47
+ assert_includes output.string, "Wrote output to #{output_path}"
48
+ end
49
+ end
50
+
51
+ def test_supports_tsv_separator
52
+ fixture = File.expand_path("../../../fixtures/sample_people.tsv", __dir__)
53
+ output = StringIO.new
54
+ input = StringIO.new("#{fixture}\n2\n\n\n\n")
55
+
56
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
57
+
58
+ assert_includes output.string, "name\tcity"
59
+ assert_includes output.string, "Alice\tLondon"
60
+ assert_includes output.string, "Bob\tParis"
61
+ assert_includes output.string, "Cara\tBerlin"
62
+ end
63
+
64
+ def test_supports_custom_separator
65
+ fixture = File.expand_path("../../../fixtures/sample_people_colon.txt", __dir__)
66
+ output = StringIO.new
67
+ input = StringIO.new("#{fixture}\n5\n:\n\n\n\n")
68
+
69
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
70
+
71
+ assert_includes output.string, "name:city"
72
+ assert_includes output.string, "Alice:London"
73
+ assert_includes output.string, "Bob:Paris"
74
+ assert_includes output.string, "Cara:Berlin"
75
+ end
76
+
77
+ def test_headerless_mode_randomizes_all_rows
78
+ fixture = File.expand_path("../../../fixtures/sample_people_no_headers.csv", __dir__)
79
+ output = StringIO.new
80
+ input = StringIO.new("#{fixture}\n\nn\n\n\n")
81
+
82
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
83
+
84
+ refute_includes output.string, "name,city"
85
+ assert_includes output.string, "Alice,London"
86
+ assert_includes output.string, "Bob,Paris"
87
+ assert_includes output.string, "Cara,Berlin"
88
+ end
89
+
90
+ def test_same_seed_produces_same_output_order
91
+ fixture = File.expand_path("../../../fixtures/sample_people_many.csv", __dir__)
92
+ input_data = "#{fixture}\n\n\n123\n\n"
93
+
94
+ out1 = StringIO.new
95
+ out2 = StringIO.new
96
+
97
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: StringIO.new(input_data), stdout: out1).call
98
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: StringIO.new(input_data), stdout: out2).call
99
+
100
+ rows1 = out1.string.lines.map(&:strip).select { |line| line.include?(",") && !line.start_with?("name,city") }
101
+ rows2 = out2.string.lines.map(&:strip).select { |line| line.include?(",") && !line.start_with?("name,city") }
102
+ assert_equal rows1, rows2
103
+ end
104
+
105
+ def test_invalid_seed_shows_friendly_error
106
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
107
+ output = StringIO.new
108
+ input = StringIO.new("#{fixture}\n\n\nabc\n")
109
+
110
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
111
+
112
+ assert_includes output.string, "Seed must be an integer."
113
+ end
114
+
115
+ def test_malformed_csv_shows_friendly_error
116
+ fixture = File.expand_path("../../../fixtures/sample_people_bad_tail.csv", __dir__)
117
+ output = StringIO.new
118
+ input = StringIO.new("#{fixture}\n\n\n\n\n")
119
+
120
+ Csvtool::Application::UseCases::RunRowRandomization.new(stdin: input, stdout: output).call
121
+
122
+ assert_includes output.string, "Could not parse CSV file."
123
+ end
124
+ end
@@ -11,7 +11,7 @@ class TestCli < Minitest::Test
11
11
 
12
12
  def test_menu_can_exit_cleanly
13
13
  output = StringIO.new
14
- status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("3\n"), stdout: output, stderr: StringIO.new)
14
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("4\n"), stdout: output, stderr: StringIO.new)
15
15
  assert_equal 0, status
16
16
  assert_includes output.string, "CSV Tool Menu"
17
17
  end
@@ -26,7 +26,7 @@ class TestCli < Minitest::Test
26
26
  "",
27
27
  "y",
28
28
  "",
29
- "3"
29
+ "4"
30
30
  ].join("\n") + "\n"
31
31
 
32
32
  output = StringIO.new
@@ -58,7 +58,7 @@ class TestCli < Minitest::Test
58
58
  "2",
59
59
  "3",
60
60
  "",
61
- "3"
61
+ "4"
62
62
  ].join("\n") + "\n"
63
63
 
64
64
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -79,7 +79,7 @@ class TestCli < Minitest::Test
79
79
  "0",
80
80
  "3",
81
81
  "",
82
- "3"
82
+ "4"
83
83
  ].join("\n") + "\n"
84
84
 
85
85
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -98,7 +98,7 @@ class TestCli < Minitest::Test
98
98
  "2",
99
99
  "3",
100
100
  "",
101
- "3"
101
+ "4"
102
102
  ].join("\n") + "\n"
103
103
 
104
104
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -119,7 +119,7 @@ class TestCli < Minitest::Test
119
119
  "2",
120
120
  "3",
121
121
  "",
122
- "3"
122
+ "4"
123
123
  ].join("\n") + "\n"
124
124
 
125
125
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -144,7 +144,7 @@ class TestCli < Minitest::Test
144
144
  "3",
145
145
  "2",
146
146
  output_path,
147
- "3"
147
+ "4"
148
148
  ].join("\n") + "\n"
149
149
 
150
150
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -164,7 +164,7 @@ class TestCli < Minitest::Test
164
164
  "1",
165
165
  "2",
166
166
  "",
167
- "3"
167
+ "4"
168
168
  ].join("\n") + "\n"
169
169
 
170
170
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -175,6 +175,111 @@ class TestCli < Minitest::Test
175
175
  refute_includes output.string, "Could not parse CSV file."
176
176
  end
177
177
 
178
+ def test_randomize_rows_workflow_prints_header_and_all_data_rows
179
+ output = StringIO.new
180
+ input = [
181
+ "3",
182
+ fixture_path("sample_people.csv"),
183
+ "",
184
+ "",
185
+ "",
186
+ "",
187
+ "4"
188
+ ].join("\n") + "\n"
189
+
190
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
191
+
192
+ assert_equal 0, status
193
+ assert_includes output.string, "name,city"
194
+ assert_includes output.string, "Alice,London"
195
+ assert_includes output.string, "Bob,Paris"
196
+ assert_includes output.string, "Cara,Berlin"
197
+ end
198
+
199
+ def test_randomize_rows_workflow_can_write_to_file
200
+ output = StringIO.new
201
+
202
+ Dir.mktmpdir do |dir|
203
+ output_path = File.join(dir, "randomized_rows.csv")
204
+ input = [
205
+ "3",
206
+ fixture_path("sample_people.csv"),
207
+ "",
208
+ "",
209
+ "",
210
+ "2",
211
+ output_path,
212
+ "4"
213
+ ].join("\n") + "\n"
214
+
215
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
216
+
217
+ assert_equal 0, status
218
+ assert_includes output.string, "Wrote output to #{output_path}"
219
+ lines = File.read(output_path).lines.map(&:strip)
220
+ assert_equal "name,city", lines.first
221
+ assert_equal ["Alice,London", "Bob,Paris", "Cara,Berlin"].sort, lines[1..].sort
222
+ end
223
+ end
224
+
225
+ def test_randomize_rows_workflow_supports_tsv_separator
226
+ output = StringIO.new
227
+ input = [
228
+ "3",
229
+ fixture_path("sample_people.tsv"),
230
+ "2",
231
+ "",
232
+ "",
233
+ "",
234
+ "4"
235
+ ].join("\n") + "\n"
236
+
237
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
238
+
239
+ assert_equal 0, status
240
+ assert_includes output.string, "name\tcity"
241
+ assert_includes output.string, "Alice\tLondon"
242
+ end
243
+
244
+ def test_randomize_rows_workflow_headerless_mode_randomizes_all_rows
245
+ output = StringIO.new
246
+ input = [
247
+ "3",
248
+ fixture_path("sample_people_no_headers.csv"),
249
+ "",
250
+ "n",
251
+ "",
252
+ "",
253
+ "4"
254
+ ].join("\n") + "\n"
255
+
256
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
257
+
258
+ assert_equal 0, status
259
+ refute_includes output.string, "name,city"
260
+ assert_includes output.string, "Alice,London"
261
+ assert_includes output.string, "Bob,Paris"
262
+ assert_includes output.string, "Cara,Berlin"
263
+ end
264
+
265
+ def test_randomize_rows_invalid_seed_returns_to_menu
266
+ output = StringIO.new
267
+ input = [
268
+ "3",
269
+ fixture_path("sample_people.csv"),
270
+ "",
271
+ "",
272
+ "abc",
273
+ "4"
274
+ ].join("\n") + "\n"
275
+
276
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
277
+
278
+ assert_equal 0, status
279
+ assert_includes output.string, "Seed must be an integer."
280
+ assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
281
+ end
282
+
178
283
  def test_end_to_end_file_output_writes_expected_csv
179
284
  output = StringIO.new
180
285
  output_path = nil
@@ -191,7 +296,7 @@ class TestCli < Minitest::Test
191
296
  "y",
192
297
  "2",
193
298
  output_path,
194
- "3"
299
+ "4"
195
300
  ].join("\n") + "\n"
196
301
 
197
302
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -211,7 +316,7 @@ class TestCli < Minitest::Test
211
316
  "1",
212
317
  "",
213
318
  "n",
214
- "3"
319
+ "4"
215
320
  ].join("\n") + "\n"
216
321
 
217
322
  output = StringIO.new
@@ -226,7 +331,7 @@ class TestCli < Minitest::Test
226
331
  output = StringIO.new
227
332
  status = Csvtool::CLI.start(
228
333
  ["menu"],
229
- stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n3\n"),
334
+ stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n4\n"),
230
335
  stdout: output,
231
336
  stderr: StringIO.new
232
337
  )
@@ -247,7 +352,7 @@ class TestCli < Minitest::Test
247
352
  "y",
248
353
  "2",
249
354
  "/tmp/not-a-dir/out.csv",
250
- "3"
355
+ "4"
251
356
  ].join("\n") + "\n"
252
357
 
253
358
  output = StringIO.new
@@ -16,7 +16,7 @@ class CliUnitTest < Minitest::Test
16
16
  end
17
17
 
18
18
  def test_menu_command_can_exit_zero
19
- status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("3\n"), stdout: StringIO.new, stderr: StringIO.new)
19
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("4\n"), stdout: StringIO.new, stderr: StringIO.new)
20
20
  assert_equal 0, status
21
21
  end
22
22
 
@@ -28,11 +28,23 @@ class CliUnitTest < Minitest::Test
28
28
  def test_menu_routes_to_row_range_shell
29
29
  stdout = StringIO.new
30
30
  fixture = File.expand_path("../fixtures/sample_people.csv", __dir__)
31
- input = ["2", fixture, "", "2", "3", "", "3"].join("\n") + "\n"
31
+ input = ["2", fixture, "", "2", "3", "", "4"].join("\n") + "\n"
32
32
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
33
33
  assert_equal 0, status
34
34
  assert_includes stdout.string, "name,city"
35
35
  assert_includes stdout.string, "Bob,Paris"
36
36
  assert_includes stdout.string, "Cara,Berlin"
37
37
  end
38
+
39
+ def test_menu_routes_to_randomize_rows_shell
40
+ stdout = StringIO.new
41
+ fixture = File.expand_path("../fixtures/sample_people.csv", __dir__)
42
+ input = ["3", fixture, "", "", "", "", "4"].join("\n") + "\n"
43
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
44
+ assert_equal 0, status
45
+ assert_includes stdout.string, "name,city"
46
+ assert_includes stdout.string, "Alice,London"
47
+ assert_includes stdout.string, "Bob,Paris"
48
+ assert_includes stdout.string, "Cara,Berlin"
49
+ end
38
50
  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,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
@@ -18,6 +18,7 @@ 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
22
23
  presenter.invalid_start_row
23
24
  presenter.invalid_end_row
@@ -35,6 +36,7 @@ class ErrorsPresenterTest < Minitest::Test
35
36
  assert_includes text, "Invalid output destination."
36
37
  assert_includes text, "Separator cannot be empty."
37
38
  assert_includes text, "Invalid separator choice."
39
+ assert_includes text, "Seed must be an integer."
38
40
  assert_includes text, "Canceled."
39
41
  assert_includes text, "Start row must be a positive integer."
40
42
  assert_includes text, "End row must be a positive integer."
@@ -19,13 +19,15 @@ class MenuLoopTest < Minitest::Test
19
19
  def test_routes_extract_column_then_exit
20
20
  column_action = FakeAction.new
21
21
  rows_action = FakeAction.new
22
+ randomize_rows_action = FakeAction.new
22
23
  stdout = StringIO.new
23
24
  menu = Csvtool::Interface::CLI::MenuLoop.new(
24
- stdin: StringIO.new("1\n3\n"),
25
+ stdin: StringIO.new("1\n4\n"),
25
26
  stdout: stdout,
26
- menu_options: ["Extract column", "Extract rows (range)", "Exit"],
27
+ menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
27
28
  extract_column_action: column_action,
28
- extract_rows_action: rows_action
29
+ extract_rows_action: rows_action,
30
+ randomize_rows_action: randomize_rows_action
29
31
  )
30
32
 
31
33
  status = menu.run
@@ -33,19 +35,22 @@ class MenuLoopTest < Minitest::Test
33
35
  assert_equal 0, status
34
36
  assert_equal 1, column_action.runs
35
37
  assert_equal 0, rows_action.runs
38
+ assert_equal 0, randomize_rows_action.runs
36
39
  assert_includes stdout.string, "CSV Tool Menu"
37
40
  end
38
41
 
39
42
  def test_routes_extract_rows_then_exit
40
43
  column_action = FakeAction.new
41
44
  rows_action = FakeAction.new
45
+ randomize_rows_action = FakeAction.new
42
46
  stdout = StringIO.new
43
47
  menu = Csvtool::Interface::CLI::MenuLoop.new(
44
- stdin: StringIO.new("2\n3\n"),
48
+ stdin: StringIO.new("2\n4\n"),
45
49
  stdout: stdout,
46
- menu_options: ["Extract column", "Extract rows (range)", "Exit"],
50
+ menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
47
51
  extract_column_action: column_action,
48
- extract_rows_action: rows_action
52
+ extract_rows_action: rows_action,
53
+ randomize_rows_action: randomize_rows_action
49
54
  )
50
55
 
51
56
  status = menu.run
@@ -53,24 +58,50 @@ class MenuLoopTest < Minitest::Test
53
58
  assert_equal 0, status
54
59
  assert_equal 0, column_action.runs
55
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
56
84
  end
57
85
 
58
86
  def test_invalid_choice_shows_prompt
59
87
  column_action = FakeAction.new
60
88
  rows_action = FakeAction.new
89
+ randomize_rows_action = FakeAction.new
61
90
  stdout = StringIO.new
62
91
  menu = Csvtool::Interface::CLI::MenuLoop.new(
63
- stdin: StringIO.new("x\n3\n"),
92
+ stdin: StringIO.new("x\n4\n"),
64
93
  stdout: stdout,
65
- menu_options: ["Extract column", "Extract rows (range)", "Exit"],
94
+ menu_options: ["Extract column", "Extract rows (range)", "Randomize rows", "Exit"],
66
95
  extract_column_action: column_action,
67
- extract_rows_action: rows_action
96
+ extract_rows_action: rows_action,
97
+ randomize_rows_action: randomize_rows_action
68
98
  )
69
99
 
70
100
  menu.run
71
101
 
72
- assert_includes stdout.string, "Please choose 1, 2, or 3."
102
+ assert_includes stdout.string, "Please choose 1, 2, 3, or 4."
73
103
  assert_equal 0, column_action.runs
74
104
  assert_equal 0, rows_action.runs
105
+ assert_equal 0, randomize_rows_action.runs
75
106
  end
76
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