csvops 0.7.0.alpha → 0.9.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 +80 -20
- data/docs/architecture.md +67 -4
- data/docs/cli-output-conventions.md +49 -0
- data/docs/release-v0.8.0-alpha.md +88 -0
- data/docs/release-v0.9.0-alpha.md +80 -0
- data/lib/csvtool/application/use_cases/run_csv_stats.rb +64 -0
- data/lib/csvtool/cli.rb +136 -12
- data/lib/csvtool/domain/csv_stats_session/stats_options.rb +11 -0
- data/lib/csvtool/domain/csv_stats_session/stats_session.rb +25 -0
- data/lib/csvtool/domain/csv_stats_session/stats_source.rb +17 -0
- data/lib/csvtool/infrastructure/csv/csv_stats_scanner.rb +67 -0
- data/lib/csvtool/infrastructure/output/csv_stats_file_writer.rb +26 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +9 -5
- data/lib/csvtool/interface/cli/output/color_policy.rb +25 -0
- data/lib/csvtool/interface/cli/output/colorizer.rb +27 -0
- data/lib/csvtool/interface/cli/output/formatters/csv_row_formatter.rb +19 -0
- data/lib/csvtool/interface/cli/output/formatters/stats_formatter.rb +57 -0
- data/lib/csvtool/interface/cli/output/streams.rb +22 -0
- data/lib/csvtool/interface/cli/output/table_renderer.rb +70 -0
- data/lib/csvtool/interface/cli/workflows/builders/csv_stats_session_builder.rb +28 -0
- data/lib/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter.rb +17 -5
- data/lib/csvtool/interface/cli/workflows/presenters/csv_parity_presenter.rb +15 -4
- data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +15 -6
- data/lib/csvtool/interface/cli/workflows/presenters/csv_stats_presenter.rb +43 -0
- data/lib/csvtool/interface/cli/workflows/presenters/row_extraction_presenter.rb +5 -4
- data/lib/csvtool/interface/cli/workflows/presenters/row_randomization_presenter.rb +5 -4
- data/lib/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow.rb +9 -8
- data/lib/csvtool/interface/cli/workflows/run_csv_parity_workflow.rb +6 -5
- data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +11 -10
- data/lib/csvtool/interface/cli/workflows/run_csv_stats_workflow.rb +78 -0
- data/lib/csvtool/interface/cli/workflows/run_extraction_workflow.rb +9 -8
- data/lib/csvtool/interface/cli/workflows/run_row_extraction_workflow.rb +7 -6
- data/lib/csvtool/interface/cli/workflows/run_row_randomization_workflow.rb +8 -7
- data/lib/csvtool/interface/cli/workflows/steps/csv_stats/build_session_step.rb +25 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_stats/collect_destination_step.rb +27 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_stats/collect_inputs_step.rb +31 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_stats/execute_step.rb +27 -0
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/application/use_cases/run_csv_stats_test.rb +165 -0
- data/test/csvtool/cli_test.rb +376 -68
- data/test/csvtool/cli_unit_test.rb +5 -5
- data/test/csvtool/infrastructure/csv/csv_stats_scanner_test.rb +68 -0
- data/test/csvtool/infrastructure/output/csv_stats_file_writer_test.rb +38 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +34 -11
- data/test/csvtool/interface/cli/output/color_policy_test.rb +40 -0
- data/test/csvtool/interface/cli/output/colorizer_test.rb +28 -0
- data/test/csvtool/interface/cli/output/formatters/csv_row_formatter_test.rb +22 -0
- data/test/csvtool/interface/cli/output/formatters/stats_formatter_test.rb +51 -0
- data/test/csvtool/interface/cli/output/streams_test.rb +25 -0
- data/test/csvtool/interface/cli/output/table_renderer_test.rb +36 -0
- data/test/csvtool/interface/cli/workflows/builders/csv_stats_session_builder_test.rb +19 -0
- data/test/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter_test.rb +4 -1
- data/test/csvtool/interface/cli/workflows/presenters/csv_parity_presenter_test.rb +5 -1
- data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +22 -4
- data/test/csvtool/interface/cli/workflows/presenters/csv_stats_presenter_test.rb +39 -0
- data/test/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow_test.rb +10 -7
- data/test/csvtool/interface/cli/workflows/run_csv_parity_workflow_test.rb +3 -1
- data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +5 -3
- data/test/csvtool/interface/cli/workflows/run_csv_stats_workflow_test.rb +151 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_stats/build_session_step_test.rb +36 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_stats/collect_destination_step_test.rb +49 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_stats/collect_inputs_step_test.rb +61 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_stats/execute_step_test.rb +65 -0
- metadata +39 -1
data/test/csvtool/cli_test.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../test_helper"
|
|
4
4
|
require "csvtool/cli"
|
|
5
|
+
require "json"
|
|
5
6
|
require "tmpdir"
|
|
6
7
|
require "fileutils"
|
|
7
8
|
|
|
@@ -12,11 +13,75 @@ class TestCli < Minitest::Test
|
|
|
12
13
|
|
|
13
14
|
def test_menu_can_exit_cleanly
|
|
14
15
|
output = StringIO.new
|
|
15
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("
|
|
16
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("8\n"), stdout: output, stderr: output)
|
|
16
17
|
assert_equal 0, status
|
|
17
18
|
assert_includes output.string, "CSV Tool Menu"
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
def test_stats_workflow_shell_can_run_and_return_to_menu
|
|
22
|
+
output = StringIO.new
|
|
23
|
+
input = [
|
|
24
|
+
"7",
|
|
25
|
+
fixture_path("sample_people.csv"),
|
|
26
|
+
"",
|
|
27
|
+
"",
|
|
28
|
+
"",
|
|
29
|
+
"8"
|
|
30
|
+
].join("\n") + "\n"
|
|
31
|
+
|
|
32
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
33
|
+
|
|
34
|
+
assert_equal 0, status
|
|
35
|
+
assert_includes output.string, "CSV Stats Summary"
|
|
36
|
+
assert_includes output.string, "Metric"
|
|
37
|
+
assert_includes output.string, "Rows"
|
|
38
|
+
assert_includes output.string, "Columns"
|
|
39
|
+
assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_stats_workflow_missing_file_returns_to_menu
|
|
43
|
+
output = StringIO.new
|
|
44
|
+
input = [
|
|
45
|
+
"7",
|
|
46
|
+
"/tmp/does-not-exist.csv",
|
|
47
|
+
"",
|
|
48
|
+
"",
|
|
49
|
+
"",
|
|
50
|
+
"8"
|
|
51
|
+
].join("\n") + "\n"
|
|
52
|
+
|
|
53
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
54
|
+
|
|
55
|
+
assert_equal 0, status
|
|
56
|
+
assert_includes output.string, "File not found: /tmp/does-not-exist.csv"
|
|
57
|
+
assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_stats_workflow_can_write_output_to_file
|
|
61
|
+
output = StringIO.new
|
|
62
|
+
|
|
63
|
+
Dir.mktmpdir do |dir|
|
|
64
|
+
output_path = File.join(dir, "stats.csv")
|
|
65
|
+
input = [
|
|
66
|
+
"7",
|
|
67
|
+
fixture_path("sample_people.csv"),
|
|
68
|
+
"",
|
|
69
|
+
"",
|
|
70
|
+
"2",
|
|
71
|
+
output_path,
|
|
72
|
+
"8"
|
|
73
|
+
].join("\n") + "\n"
|
|
74
|
+
|
|
75
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
76
|
+
|
|
77
|
+
assert_equal 0, status
|
|
78
|
+
assert_includes output.string, "Wrote output to #{output_path}"
|
|
79
|
+
csv_text = File.read(output_path)
|
|
80
|
+
assert_includes csv_text, "metric,value"
|
|
81
|
+
assert_includes csv_text, "row_count,3"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
20
85
|
def test_split_workflow_splits_csv_in_menu_flow
|
|
21
86
|
output = StringIO.new
|
|
22
87
|
Dir.mktmpdir do |dir|
|
|
@@ -32,13 +97,13 @@ class TestCli < Minitest::Test
|
|
|
32
97
|
"",
|
|
33
98
|
"",
|
|
34
99
|
"",
|
|
35
|
-
"
|
|
100
|
+
"8"
|
|
36
101
|
].join("\n") + "\n"
|
|
37
102
|
|
|
38
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
103
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
39
104
|
|
|
40
105
|
assert_equal 0, status
|
|
41
|
-
assert_includes output.string, "Chunks written
|
|
106
|
+
assert_includes output.string, "Chunks written"
|
|
42
107
|
assert File.file?(File.join(dir, "people_part_001.csv"))
|
|
43
108
|
assert File.file?(File.join(dir, "people_part_002.csv"))
|
|
44
109
|
assert File.file?(File.join(dir, "people_part_003.csv"))
|
|
@@ -53,10 +118,10 @@ class TestCli < Minitest::Test
|
|
|
53
118
|
"",
|
|
54
119
|
"",
|
|
55
120
|
"0",
|
|
56
|
-
"
|
|
121
|
+
"8"
|
|
57
122
|
].join("\n") + "\n"
|
|
58
123
|
|
|
59
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
124
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
60
125
|
|
|
61
126
|
assert_equal 0, status
|
|
62
127
|
assert_includes output.string, "Chunk size must be a positive integer."
|
|
@@ -73,11 +138,11 @@ class TestCli < Minitest::Test
|
|
|
73
138
|
"",
|
|
74
139
|
"y",
|
|
75
140
|
"",
|
|
76
|
-
"
|
|
141
|
+
"8"
|
|
77
142
|
].join("\n") + "\n"
|
|
78
143
|
|
|
79
144
|
output = StringIO.new
|
|
80
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
145
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
81
146
|
|
|
82
147
|
assert_equal 0, status
|
|
83
148
|
assert_match(/\nAlice\nBob\nCara\n/, output.string)
|
|
@@ -96,6 +161,244 @@ class TestCli < Minitest::Test
|
|
|
96
161
|
assert_equal "Alice\nBob\nCara\n", output.string
|
|
97
162
|
end
|
|
98
163
|
|
|
164
|
+
def test_stats_command_writes_summary_to_stdout_only
|
|
165
|
+
stdout = StringIO.new
|
|
166
|
+
stderr = StringIO.new
|
|
167
|
+
|
|
168
|
+
status = Csvtool::CLI.start(
|
|
169
|
+
["stats", fixture_path("sample_people.csv")],
|
|
170
|
+
stdin: StringIO.new,
|
|
171
|
+
stdout: stdout,
|
|
172
|
+
stderr: stderr
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
assert_equal 0, status
|
|
176
|
+
assert_includes stdout.string, "CSV Stats Summary"
|
|
177
|
+
assert_includes stdout.string, "Metric"
|
|
178
|
+
assert_includes stdout.string, "Rows"
|
|
179
|
+
assert_includes stdout.string, "Columns"
|
|
180
|
+
assert_includes stdout.string, "Headers"
|
|
181
|
+
assert_equal "", stderr.string
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_stats_command_writes_errors_to_stderr_only
|
|
185
|
+
stdout = StringIO.new
|
|
186
|
+
stderr = StringIO.new
|
|
187
|
+
|
|
188
|
+
status = Csvtool::CLI.start(
|
|
189
|
+
["stats", "/tmp/not-there.csv"],
|
|
190
|
+
stdin: StringIO.new,
|
|
191
|
+
stdout: stdout,
|
|
192
|
+
stderr: stderr
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
assert_equal 1, status
|
|
196
|
+
assert_equal "", stdout.string
|
|
197
|
+
assert_includes stderr.string, "File not found: /tmp/not-there.csv"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_stats_command_supports_json_format
|
|
201
|
+
stdout = StringIO.new
|
|
202
|
+
stderr = StringIO.new
|
|
203
|
+
|
|
204
|
+
status = Csvtool::CLI.start(
|
|
205
|
+
["stats", fixture_path("sample_people.csv"), "--format", "json"],
|
|
206
|
+
stdin: StringIO.new,
|
|
207
|
+
stdout: stdout,
|
|
208
|
+
stderr: stderr
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
assert_equal 0, status
|
|
212
|
+
data = JSON.parse(stdout.string, symbolize_names: true)
|
|
213
|
+
assert_equal 3, data[:row_count]
|
|
214
|
+
assert_equal 2, data[:column_count]
|
|
215
|
+
assert_equal ["name", "city"], data[:headers]
|
|
216
|
+
assert_equal "", stderr.string
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_stats_command_supports_csv_format
|
|
220
|
+
stdout = StringIO.new
|
|
221
|
+
stderr = StringIO.new
|
|
222
|
+
|
|
223
|
+
status = Csvtool::CLI.start(
|
|
224
|
+
["stats", fixture_path("sample_people.csv"), "--format=csv"],
|
|
225
|
+
stdin: StringIO.new,
|
|
226
|
+
stdout: stdout,
|
|
227
|
+
stderr: stderr
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
assert_equal 0, status
|
|
231
|
+
lines = stdout.string.lines.map(&:strip)
|
|
232
|
+
assert_equal "metric,value", lines[0]
|
|
233
|
+
assert_includes lines, "row_count,3"
|
|
234
|
+
assert_includes lines, "column_count,2"
|
|
235
|
+
assert_includes lines, "headers,name|city"
|
|
236
|
+
assert_equal "", stderr.string
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_stats_command_rejects_unknown_format
|
|
240
|
+
stdout = StringIO.new
|
|
241
|
+
stderr = StringIO.new
|
|
242
|
+
|
|
243
|
+
status = Csvtool::CLI.start(
|
|
244
|
+
["stats", fixture_path("sample_people.csv"), "--format", "yaml"],
|
|
245
|
+
stdin: StringIO.new,
|
|
246
|
+
stdout: stdout,
|
|
247
|
+
stderr: stderr
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
assert_equal 1, status
|
|
251
|
+
assert_equal "", stdout.string
|
|
252
|
+
assert_includes stderr.string, "Invalid format: yaml"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def test_stats_command_color_auto_without_tty_has_no_ansi
|
|
256
|
+
stdout = StringIO.new
|
|
257
|
+
stderr = StringIO.new
|
|
258
|
+
|
|
259
|
+
status = Csvtool::CLI.start(
|
|
260
|
+
["stats", fixture_path("sample_people.csv"), "--color", "auto"],
|
|
261
|
+
stdin: StringIO.new,
|
|
262
|
+
stdout: stdout,
|
|
263
|
+
stderr: stderr
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert_equal 0, status
|
|
267
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
268
|
+
assert_equal "", stderr.string
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_stats_command_color_always_adds_ansi
|
|
272
|
+
stdout = StringIO.new
|
|
273
|
+
stderr = StringIO.new
|
|
274
|
+
|
|
275
|
+
status = Csvtool::CLI.start(
|
|
276
|
+
["stats", fixture_path("sample_people.csv"), "--color=always"],
|
|
277
|
+
stdin: StringIO.new,
|
|
278
|
+
stdout: stdout,
|
|
279
|
+
stderr: stderr
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
assert_equal 0, status
|
|
283
|
+
assert_match(/\e\[[0-9;]*m/, stdout.string)
|
|
284
|
+
assert_equal "", stderr.string
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_stats_command_color_never_disables_ansi
|
|
288
|
+
stdout = StringIO.new
|
|
289
|
+
stderr = StringIO.new
|
|
290
|
+
|
|
291
|
+
status = Csvtool::CLI.start(
|
|
292
|
+
["stats", fixture_path("sample_people.csv"), "--color", "never"],
|
|
293
|
+
stdin: StringIO.new,
|
|
294
|
+
stdout: stdout,
|
|
295
|
+
stderr: stderr
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
assert_equal 0, status
|
|
299
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
300
|
+
assert_equal "", stderr.string
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def test_stats_command_no_color_env_disables_auto
|
|
304
|
+
stdout = StringIO.new
|
|
305
|
+
stderr = StringIO.new
|
|
306
|
+
|
|
307
|
+
status = Csvtool::CLI.start(
|
|
308
|
+
["stats", fixture_path("sample_people.csv"), "--color", "auto"],
|
|
309
|
+
stdin: StringIO.new,
|
|
310
|
+
stdout: stdout,
|
|
311
|
+
stderr: stderr,
|
|
312
|
+
env: { "NO_COLOR" => "1" }
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
assert_equal 0, status
|
|
316
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def test_stats_command_color_always_overrides_no_color_env
|
|
320
|
+
stdout = StringIO.new
|
|
321
|
+
stderr = StringIO.new
|
|
322
|
+
|
|
323
|
+
status = Csvtool::CLI.start(
|
|
324
|
+
["stats", fixture_path("sample_people.csv"), "--color", "always"],
|
|
325
|
+
stdin: StringIO.new,
|
|
326
|
+
stdout: stdout,
|
|
327
|
+
stderr: stderr,
|
|
328
|
+
env: { "NO_COLOR" => "1" }
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
assert_equal 0, status
|
|
332
|
+
assert_match(/\e\[[0-9;]*m/, stdout.string)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def test_stats_command_rejects_unknown_color_mode
|
|
336
|
+
stdout = StringIO.new
|
|
337
|
+
stderr = StringIO.new
|
|
338
|
+
|
|
339
|
+
status = Csvtool::CLI.start(
|
|
340
|
+
["stats", fixture_path("sample_people.csv"), "--color", "sometimes"],
|
|
341
|
+
stdin: StringIO.new,
|
|
342
|
+
stdout: stdout,
|
|
343
|
+
stderr: stderr
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
assert_equal 1, status
|
|
347
|
+
assert_equal "", stdout.string
|
|
348
|
+
assert_includes stderr.string, "Invalid color mode: sometimes"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def test_menu_workflow_routes_ui_to_stderr_and_data_to_stdout
|
|
352
|
+
stdout = StringIO.new
|
|
353
|
+
stderr = StringIO.new
|
|
354
|
+
input = [
|
|
355
|
+
"7",
|
|
356
|
+
fixture_path("sample_people.csv"),
|
|
357
|
+
"",
|
|
358
|
+
"",
|
|
359
|
+
"",
|
|
360
|
+
"8"
|
|
361
|
+
].join("\n") + "\n"
|
|
362
|
+
|
|
363
|
+
status = Csvtool::CLI.start(
|
|
364
|
+
["menu"],
|
|
365
|
+
stdin: StringIO.new(input),
|
|
366
|
+
stdout: stdout,
|
|
367
|
+
stderr: stderr
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
assert_equal 0, status
|
|
371
|
+
assert_includes stderr.string, "CSV Tool Menu"
|
|
372
|
+
assert_includes stderr.string, "CSV file path:"
|
|
373
|
+
assert_includes stdout.string, "CSV Stats Summary"
|
|
374
|
+
refute_includes stdout.string, "CSV Tool Menu"
|
|
375
|
+
refute_includes stdout.string, "CSV file path:"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def test_stats_command_table_respects_narrow_terminal_width
|
|
379
|
+
stdout = StringIO.new
|
|
380
|
+
stderr = StringIO.new
|
|
381
|
+
|
|
382
|
+
Dir.mktmpdir do |dir|
|
|
383
|
+
path = File.join(dir, "long_headers.csv")
|
|
384
|
+
File.write(path, "very_long_column_name,another_really_long_column_name\nvalue_a,value_b\n")
|
|
385
|
+
|
|
386
|
+
status = Csvtool::CLI.start(
|
|
387
|
+
["stats", path],
|
|
388
|
+
stdin: StringIO.new,
|
|
389
|
+
stdout: stdout,
|
|
390
|
+
stderr: stderr,
|
|
391
|
+
env: { "COLUMNS" => "32" }
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
assert_equal 0, status
|
|
395
|
+
lines = stdout.string.lines.map(&:chomp)
|
|
396
|
+
assert lines.all? { |line| line.length <= 32 }
|
|
397
|
+
assert_includes stdout.string, "..."
|
|
398
|
+
assert_equal "", stderr.string
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
99
402
|
def test_row_range_workflow_prints_selected_rows
|
|
100
403
|
output = StringIO.new
|
|
101
404
|
input = [
|
|
@@ -105,10 +408,10 @@ class TestCli < Minitest::Test
|
|
|
105
408
|
"2",
|
|
106
409
|
"3",
|
|
107
410
|
"",
|
|
108
|
-
"
|
|
411
|
+
"8"
|
|
109
412
|
].join("\n") + "\n"
|
|
110
413
|
|
|
111
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
414
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
112
415
|
|
|
113
416
|
assert_equal 0, status
|
|
114
417
|
assert_includes output.string, "name,city"
|
|
@@ -126,10 +429,10 @@ class TestCli < Minitest::Test
|
|
|
126
429
|
"0",
|
|
127
430
|
"3",
|
|
128
431
|
"",
|
|
129
|
-
"
|
|
432
|
+
"8"
|
|
130
433
|
].join("\n") + "\n"
|
|
131
434
|
|
|
132
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
435
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
133
436
|
|
|
134
437
|
assert_equal 0, status
|
|
135
438
|
assert_includes output.string, "Start row must be a positive integer."
|
|
@@ -145,10 +448,10 @@ class TestCli < Minitest::Test
|
|
|
145
448
|
"2",
|
|
146
449
|
"3",
|
|
147
450
|
"",
|
|
148
|
-
"
|
|
451
|
+
"8"
|
|
149
452
|
].join("\n") + "\n"
|
|
150
453
|
|
|
151
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
454
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
152
455
|
|
|
153
456
|
assert_equal 0, status
|
|
154
457
|
assert_includes output.string, "name,city"
|
|
@@ -166,10 +469,10 @@ class TestCli < Minitest::Test
|
|
|
166
469
|
"2",
|
|
167
470
|
"3",
|
|
168
471
|
"",
|
|
169
|
-
"
|
|
472
|
+
"8"
|
|
170
473
|
].join("\n") + "\n"
|
|
171
474
|
|
|
172
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
475
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
173
476
|
|
|
174
477
|
assert_equal 0, status
|
|
175
478
|
assert_includes output.string, "name,city"
|
|
@@ -191,10 +494,10 @@ class TestCli < Minitest::Test
|
|
|
191
494
|
"3",
|
|
192
495
|
"2",
|
|
193
496
|
output_path,
|
|
194
|
-
"
|
|
497
|
+
"8"
|
|
195
498
|
].join("\n") + "\n"
|
|
196
499
|
|
|
197
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
500
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
198
501
|
assert_equal 0, status
|
|
199
502
|
assert_equal "name,city\nBob,Paris\nCara,Berlin\n", File.read(output_path)
|
|
200
503
|
end
|
|
@@ -211,10 +514,10 @@ class TestCli < Minitest::Test
|
|
|
211
514
|
"1",
|
|
212
515
|
"2",
|
|
213
516
|
"",
|
|
214
|
-
"
|
|
517
|
+
"8"
|
|
215
518
|
].join("\n") + "\n"
|
|
216
519
|
|
|
217
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
520
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
218
521
|
|
|
219
522
|
assert_equal 0, status
|
|
220
523
|
assert_includes output.string, "Alice,London"
|
|
@@ -231,10 +534,10 @@ class TestCli < Minitest::Test
|
|
|
231
534
|
"",
|
|
232
535
|
"",
|
|
233
536
|
"",
|
|
234
|
-
"
|
|
537
|
+
"8"
|
|
235
538
|
].join("\n") + "\n"
|
|
236
539
|
|
|
237
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
540
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
238
541
|
|
|
239
542
|
assert_equal 0, status
|
|
240
543
|
assert_includes output.string, "name,city"
|
|
@@ -256,10 +559,10 @@ class TestCli < Minitest::Test
|
|
|
256
559
|
"",
|
|
257
560
|
"2",
|
|
258
561
|
output_path,
|
|
259
|
-
"
|
|
562
|
+
"8"
|
|
260
563
|
].join("\n") + "\n"
|
|
261
564
|
|
|
262
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
565
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
263
566
|
|
|
264
567
|
assert_equal 0, status
|
|
265
568
|
assert_includes output.string, "Wrote output to #{output_path}"
|
|
@@ -278,10 +581,10 @@ class TestCli < Minitest::Test
|
|
|
278
581
|
"",
|
|
279
582
|
"",
|
|
280
583
|
"",
|
|
281
|
-
"
|
|
584
|
+
"8"
|
|
282
585
|
].join("\n") + "\n"
|
|
283
586
|
|
|
284
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
587
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
285
588
|
|
|
286
589
|
assert_equal 0, status
|
|
287
590
|
assert_includes output.string, "name\tcity"
|
|
@@ -297,10 +600,10 @@ class TestCli < Minitest::Test
|
|
|
297
600
|
"n",
|
|
298
601
|
"",
|
|
299
602
|
"",
|
|
300
|
-
"
|
|
603
|
+
"8"
|
|
301
604
|
].join("\n") + "\n"
|
|
302
605
|
|
|
303
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
606
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
304
607
|
|
|
305
608
|
assert_equal 0, status
|
|
306
609
|
refute_includes output.string, "name,city"
|
|
@@ -317,10 +620,10 @@ class TestCli < Minitest::Test
|
|
|
317
620
|
"",
|
|
318
621
|
"",
|
|
319
622
|
"abc",
|
|
320
|
-
"
|
|
623
|
+
"8"
|
|
321
624
|
].join("\n") + "\n"
|
|
322
625
|
|
|
323
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
626
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
324
627
|
|
|
325
628
|
assert_equal 0, status
|
|
326
629
|
assert_includes output.string, "Seed must be an integer."
|
|
@@ -342,10 +645,10 @@ class TestCli < Minitest::Test
|
|
|
342
645
|
"",
|
|
343
646
|
"",
|
|
344
647
|
"",
|
|
345
|
-
"
|
|
648
|
+
"8"
|
|
346
649
|
].join("\n") + "\n"
|
|
347
650
|
|
|
348
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
651
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
349
652
|
|
|
350
653
|
assert_equal 0, status
|
|
351
654
|
assert_includes output.string, "Reference CSV file path:"
|
|
@@ -354,7 +657,7 @@ class TestCli < Minitest::Test
|
|
|
354
657
|
assert_includes output.string, "customer_id,name"
|
|
355
658
|
assert_includes output.string, "1,Alice"
|
|
356
659
|
assert_includes output.string, "3,Cara"
|
|
357
|
-
assert_includes output.string, "Summary
|
|
660
|
+
assert_includes output.string, "Summary"
|
|
358
661
|
end
|
|
359
662
|
|
|
360
663
|
def test_dedupe_workflow_can_write_to_file
|
|
@@ -376,15 +679,15 @@ class TestCli < Minitest::Test
|
|
|
376
679
|
"",
|
|
377
680
|
"2",
|
|
378
681
|
output_path,
|
|
379
|
-
"
|
|
682
|
+
"8"
|
|
380
683
|
].join("\n") + "\n"
|
|
381
684
|
|
|
382
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
685
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
383
686
|
|
|
384
687
|
assert_equal 0, status
|
|
385
688
|
assert_includes output.string, "Wrote output to #{output_path}"
|
|
386
689
|
assert_equal "customer_id,name\n1,Alice\n3,Cara\n", File.read(output_path)
|
|
387
|
-
assert_includes output.string, "Summary
|
|
690
|
+
assert_includes output.string, "Summary"
|
|
388
691
|
end
|
|
389
692
|
end
|
|
390
693
|
|
|
@@ -403,10 +706,10 @@ class TestCli < Minitest::Test
|
|
|
403
706
|
"",
|
|
404
707
|
"",
|
|
405
708
|
"",
|
|
406
|
-
"
|
|
709
|
+
"8"
|
|
407
710
|
].join("\n") + "\n"
|
|
408
711
|
|
|
409
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
712
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
410
713
|
|
|
411
714
|
assert_equal 0, status
|
|
412
715
|
assert_includes output.string, "customer_id\tname"
|
|
@@ -429,16 +732,16 @@ class TestCli < Minitest::Test
|
|
|
429
732
|
"",
|
|
430
733
|
"",
|
|
431
734
|
"",
|
|
432
|
-
"
|
|
735
|
+
"8"
|
|
433
736
|
].join("\n") + "\n"
|
|
434
737
|
|
|
435
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
738
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
436
739
|
|
|
437
740
|
assert_equal 0, status
|
|
438
741
|
refute_includes output.string, "customer_id,name"
|
|
439
742
|
assert_includes output.string, "1,Alice"
|
|
440
743
|
assert_includes output.string, "3,Cara"
|
|
441
|
-
assert_includes output.string, "Summary
|
|
744
|
+
assert_includes output.string, "Summary"
|
|
442
745
|
end
|
|
443
746
|
|
|
444
747
|
def test_parity_workflow_reports_match_and_returns_to_menu
|
|
@@ -449,16 +752,18 @@ class TestCli < Minitest::Test
|
|
|
449
752
|
fixture_path("sample_people.csv"),
|
|
450
753
|
"",
|
|
451
754
|
"",
|
|
452
|
-
"
|
|
755
|
+
"8"
|
|
453
756
|
].join("\n") + "\n"
|
|
454
757
|
|
|
455
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
758
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
456
759
|
|
|
457
760
|
assert_equal 0, status
|
|
458
761
|
assert_includes output.string, "Left CSV file path:"
|
|
459
762
|
assert_includes output.string, "Right CSV file path:"
|
|
460
763
|
assert_includes output.string, "MATCH"
|
|
461
|
-
assert_includes output.string, "
|
|
764
|
+
assert_includes output.string, "Metric"
|
|
765
|
+
assert_includes output.string, "Left rows"
|
|
766
|
+
assert_includes output.string, "Right rows"
|
|
462
767
|
assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
|
|
463
768
|
end
|
|
464
769
|
|
|
@@ -470,14 +775,15 @@ class TestCli < Minitest::Test
|
|
|
470
775
|
fixture_path("parity_people_reordered.tsv"),
|
|
471
776
|
"2",
|
|
472
777
|
"",
|
|
473
|
-
"
|
|
778
|
+
"8"
|
|
474
779
|
].join("\n") + "\n"
|
|
475
780
|
|
|
476
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
781
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
477
782
|
|
|
478
783
|
assert_equal 0, status
|
|
479
784
|
assert_includes output.string, "MATCH"
|
|
480
|
-
assert_includes output.string, "
|
|
785
|
+
assert_includes output.string, "Left rows"
|
|
786
|
+
assert_includes output.string, "Right rows"
|
|
481
787
|
end
|
|
482
788
|
|
|
483
789
|
def test_parity_workflow_headerless_mode_compares_all_rows
|
|
@@ -488,14 +794,15 @@ class TestCli < Minitest::Test
|
|
|
488
794
|
fixture_path("sample_people_no_headers.csv"),
|
|
489
795
|
"",
|
|
490
796
|
"n",
|
|
491
|
-
"
|
|
797
|
+
"8"
|
|
492
798
|
].join("\n") + "\n"
|
|
493
799
|
|
|
494
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
800
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
495
801
|
|
|
496
802
|
assert_equal 0, status
|
|
497
803
|
assert_includes output.string, "MATCH"
|
|
498
|
-
assert_includes output.string, "
|
|
804
|
+
assert_includes output.string, "Left rows"
|
|
805
|
+
assert_includes output.string, "Right rows"
|
|
499
806
|
end
|
|
500
807
|
|
|
501
808
|
def test_parity_workflow_reports_header_mismatch_in_headered_mode
|
|
@@ -506,10 +813,10 @@ class TestCli < Minitest::Test
|
|
|
506
813
|
fixture_path("parity_people_header_mismatch.csv"),
|
|
507
814
|
"",
|
|
508
815
|
"",
|
|
509
|
-
"
|
|
816
|
+
"8"
|
|
510
817
|
].join("\n") + "\n"
|
|
511
818
|
|
|
512
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
819
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
513
820
|
|
|
514
821
|
assert_equal 0, status
|
|
515
822
|
assert_includes output.string, "CSV headers do not match."
|
|
@@ -524,14 +831,15 @@ class TestCli < Minitest::Test
|
|
|
524
831
|
fixture_path("parity_people_mismatch.csv"),
|
|
525
832
|
"",
|
|
526
833
|
"",
|
|
527
|
-
"
|
|
834
|
+
"8"
|
|
528
835
|
].join("\n") + "\n"
|
|
529
836
|
|
|
530
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
837
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
531
838
|
|
|
532
839
|
assert_equal 0, status
|
|
533
840
|
assert_includes output.string, "MISMATCH"
|
|
534
|
-
assert_includes output.string, "
|
|
841
|
+
assert_includes output.string, "Left only"
|
|
842
|
+
assert_includes output.string, "Right only"
|
|
535
843
|
assert_includes output.string, "Left-only examples:"
|
|
536
844
|
assert_includes output.string, "Cara,Berlin (count +1)"
|
|
537
845
|
assert_includes output.string, "Right-only examples:"
|
|
@@ -546,10 +854,10 @@ class TestCli < Minitest::Test
|
|
|
546
854
|
fixture_path("sample_people.csv"),
|
|
547
855
|
"",
|
|
548
856
|
"",
|
|
549
|
-
"
|
|
857
|
+
"8"
|
|
550
858
|
].join("\n") + "\n"
|
|
551
859
|
|
|
552
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
860
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
553
861
|
|
|
554
862
|
assert_equal 0, status
|
|
555
863
|
assert_includes output.string, "File not found: /tmp/not-there-left.csv"
|
|
@@ -565,10 +873,10 @@ class TestCli < Minitest::Test
|
|
|
565
873
|
"/tmp/not-there-right.csv",
|
|
566
874
|
"",
|
|
567
875
|
"",
|
|
568
|
-
"
|
|
876
|
+
"8"
|
|
569
877
|
].join("\n") + "\n"
|
|
570
878
|
|
|
571
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
879
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
572
880
|
|
|
573
881
|
assert_equal 0, status
|
|
574
882
|
assert_includes output.string, "File not found: /tmp/not-there-right.csv"
|
|
@@ -584,10 +892,10 @@ class TestCli < Minitest::Test
|
|
|
584
892
|
fixture_path("sample_people_bad_tail.csv"),
|
|
585
893
|
"",
|
|
586
894
|
"",
|
|
587
|
-
"
|
|
895
|
+
"8"
|
|
588
896
|
].join("\n") + "\n"
|
|
589
897
|
|
|
590
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
898
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
591
899
|
|
|
592
900
|
assert_equal 0, status
|
|
593
901
|
assert_includes output.string, "Could not parse CSV file."
|
|
@@ -611,10 +919,10 @@ class TestCli < Minitest::Test
|
|
|
611
919
|
"y",
|
|
612
920
|
"2",
|
|
613
921
|
output_path,
|
|
614
|
-
"
|
|
922
|
+
"8"
|
|
615
923
|
].join("\n") + "\n"
|
|
616
924
|
|
|
617
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
925
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
618
926
|
assert_equal 0, status
|
|
619
927
|
assert_equal "name\nAlice\nBob\nCara\n", File.read(output_path)
|
|
620
928
|
end
|
|
@@ -631,11 +939,11 @@ class TestCli < Minitest::Test
|
|
|
631
939
|
"1",
|
|
632
940
|
"",
|
|
633
941
|
"n",
|
|
634
|
-
"
|
|
942
|
+
"8"
|
|
635
943
|
].join("\n") + "\n"
|
|
636
944
|
|
|
637
945
|
output = StringIO.new
|
|
638
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
946
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
639
947
|
|
|
640
948
|
assert_equal 0, status
|
|
641
949
|
assert_includes output.string, "Canceled."
|
|
@@ -648,7 +956,7 @@ class TestCli < Minitest::Test
|
|
|
648
956
|
["menu"],
|
|
649
957
|
stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n4\n7\n"),
|
|
650
958
|
stdout: output,
|
|
651
|
-
stderr:
|
|
959
|
+
stderr: output
|
|
652
960
|
)
|
|
653
961
|
|
|
654
962
|
assert_equal 0, status
|
|
@@ -667,11 +975,11 @@ class TestCli < Minitest::Test
|
|
|
667
975
|
"y",
|
|
668
976
|
"2",
|
|
669
977
|
"/tmp/not-a-dir/out.csv",
|
|
670
|
-
"
|
|
978
|
+
"8"
|
|
671
979
|
].join("\n") + "\n"
|
|
672
980
|
|
|
673
981
|
output = StringIO.new
|
|
674
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
982
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
675
983
|
|
|
676
984
|
assert_equal 0, status
|
|
677
985
|
assert_includes output.string, "Cannot write output file: /tmp/not-a-dir/out.csv"
|