csvops 0.6.0.alpha → 0.7.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -12
  3. data/docs/architecture.md +61 -4
  4. data/docs/release-v0.7.0-alpha.md +87 -0
  5. data/lib/csvtool/application/use_cases/run_csv_split.rb +97 -0
  6. data/lib/csvtool/cli.rb +5 -1
  7. data/lib/csvtool/domain/csv_split_session/split_options.rb +27 -0
  8. data/lib/csvtool/domain/csv_split_session/split_session.rb +20 -0
  9. data/lib/csvtool/domain/csv_split_session/split_source.rb +17 -0
  10. data/lib/csvtool/infrastructure/csv/csv_splitter.rb +64 -0
  11. data/lib/csvtool/infrastructure/output/csv_split_manifest_writer.rb +20 -0
  12. data/lib/csvtool/interface/cli/errors/presenter.rb +8 -0
  13. data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
  14. data/lib/csvtool/interface/cli/prompts/chunk_size_prompt.rb +21 -0
  15. data/lib/csvtool/interface/cli/prompts/split_manifest_prompt.rb +30 -0
  16. data/lib/csvtool/interface/cli/prompts/split_output_prompt.rb +38 -0
  17. data/lib/csvtool/interface/cli/workflows/builders/csv_split_session_builder.rb +44 -0
  18. data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +26 -0
  19. data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +89 -0
  20. data/lib/csvtool/interface/cli/workflows/steps/csv_split/build_session_step.rb +30 -0
  21. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step.rb +43 -0
  22. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step.rb +30 -0
  23. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step.rb +31 -0
  24. data/lib/csvtool/interface/cli/workflows/steps/csv_split/execute_step.rb +36 -0
  25. data/lib/csvtool/version.rb +1 -1
  26. data/test/csvtool/application/use_cases/run_csv_split_test.rb +124 -0
  27. data/test/csvtool/cli_test.rb +76 -29
  28. data/test/csvtool/infrastructure/csv/csv_splitter_test.rb +68 -0
  29. data/test/csvtool/infrastructure/output/csv_split_manifest_writer_test.rb +25 -0
  30. data/test/csvtool/interface/cli/menu_loop_test.rb +81 -130
  31. data/test/csvtool/interface/cli/prompts/chunk_size_prompt_test.rb +17 -0
  32. data/test/csvtool/interface/cli/prompts/split_manifest_prompt_test.rb +42 -0
  33. data/test/csvtool/interface/cli/prompts/split_output_prompt_test.rb +22 -0
  34. data/test/csvtool/interface/cli/workflows/builders/csv_split_session_builder_test.rb +30 -0
  35. data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +26 -0
  36. data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +200 -0
  37. data/test/csvtool/interface/cli/workflows/steps/csv_split/build_session_step_test.rb +40 -0
  38. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step_test.rb +64 -0
  39. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step_test.rb +30 -0
  40. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step_test.rb +32 -0
  41. data/test/csvtool/interface/cli/workflows/steps/csv_split/execute_step_test.rb +83 -0
  42. data/test/fixtures/split_people_25.csv +26 -0
  43. metadata +34 -1
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/split_manifest_prompt"
5
+
6
+ class SplitManifestPromptTest < Minitest::Test
7
+ class FakeYesNoPrompt
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+
12
+ def call(label:, default:)
13
+ @value.nil? ? default : @value
14
+ end
15
+ end
16
+
17
+ def test_returns_disabled_when_user_declines_manifest
18
+ prompt = Csvtool::Interface::CLI::Prompts::SplitManifestPrompt.new(
19
+ stdin: StringIO.new,
20
+ stdout: StringIO.new,
21
+ yes_no_prompt: FakeYesNoPrompt.new(false)
22
+ )
23
+
24
+ result = prompt.call(default_path: "/tmp/manifest.csv")
25
+
26
+ assert_equal false, result[:write_manifest]
27
+ assert_nil result[:manifest_path]
28
+ end
29
+
30
+ def test_uses_default_manifest_path_when_blank
31
+ prompt = Csvtool::Interface::CLI::Prompts::SplitManifestPrompt.new(
32
+ stdin: StringIO.new("\n"),
33
+ stdout: StringIO.new,
34
+ yes_no_prompt: FakeYesNoPrompt.new(true)
35
+ )
36
+
37
+ result = prompt.call(default_path: "/tmp/manifest.csv")
38
+
39
+ assert_equal true, result[:write_manifest]
40
+ assert_equal "/tmp/manifest.csv", result[:manifest_path]
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/prompts/split_output_prompt"
5
+ require "csvtool/interface/cli/prompts/yes_no_prompt"
6
+
7
+ class SplitOutputPromptTest < Minitest::Test
8
+ def test_uses_defaults_for_blank_values
9
+ out = StringIO.new
10
+ prompt = Csvtool::Interface::CLI::Prompts::SplitOutputPrompt.new(
11
+ stdin: StringIO.new("\n\n\n"),
12
+ stdout: out,
13
+ yes_no_prompt: Csvtool::Interface::CLI::Prompts::YesNoPrompt.new(stdin: StringIO.new("\n"), stdout: StringIO.new)
14
+ )
15
+
16
+ result = prompt.call(default_directory: "/tmp/out", default_prefix: "people")
17
+
18
+ assert_equal "/tmp/out", result[:output_directory]
19
+ assert_equal "people", result[:file_prefix]
20
+ assert_equal false, result[:overwrite_existing]
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/builders/csv_split_session_builder"
5
+
6
+ class CsvSplitSessionBuilderTest < Minitest::Test
7
+ def test_builds_split_session
8
+ session = Csvtool::Interface::CLI::Workflows::Builders::CsvSplitSessionBuilder.new.call(
9
+ file_path: "/tmp/people.csv",
10
+ col_sep: ",",
11
+ headers_present: true,
12
+ chunk_size: 10,
13
+ output_directory: "/tmp/out",
14
+ file_prefix: "batch",
15
+ overwrite_existing: true,
16
+ write_manifest: true,
17
+ manifest_path: "/tmp/out/manifest.csv"
18
+ )
19
+
20
+ assert_equal "/tmp/people.csv", session.source.path
21
+ assert_equal ",", session.source.separator
22
+ assert_equal true, session.source.headers_present
23
+ assert_equal 10, session.options.chunk_size
24
+ assert_equal "/tmp/out", session.options.output_directory
25
+ assert_equal "batch", session.options.file_prefix
26
+ assert_equal true, session.options.overwrite_existing
27
+ assert_equal true, session.options.write_manifest
28
+ assert_equal "/tmp/out/manifest.csv", session.options.manifest_path
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/presenters/csv_split_presenter"
5
+
6
+ class CsvSplitPresenterTest < Minitest::Test
7
+ def test_prints_summary_and_chunk_paths
8
+ out = StringIO.new
9
+ presenter = Csvtool::Interface::CLI::Workflows::Presenters::CsvSplitPresenter.new(stdout: out)
10
+
11
+ presenter.print_summary(
12
+ chunk_size: 10,
13
+ data_rows: 25,
14
+ chunk_count: 3,
15
+ manifest_path: "/tmp/manifest.csv",
16
+ chunk_paths: ["/tmp/people_part_001.csv", "/tmp/people_part_002.csv", "/tmp/people_part_003.csv"]
17
+ )
18
+
19
+ assert_includes out.string, "Split complete."
20
+ assert_includes out.string, "Chunk size: 10"
21
+ assert_includes out.string, "Data rows: 25"
22
+ assert_includes out.string, "Chunks written: 3"
23
+ assert_includes out.string, "Manifest: /tmp/manifest.csv"
24
+ assert_includes out.string, "/tmp/people_part_001.csv"
25
+ end
26
+ end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/run_csv_split_workflow"
5
+ require "tmpdir"
6
+ require "fileutils"
7
+
8
+ class RunCsvSplitWorkflowTest < Minitest::Test
9
+ def fixture_path(name)
10
+ File.expand_path("../../../../fixtures/#{name}", __dir__)
11
+ end
12
+
13
+ def test_workflow_splits_into_chunk_files
14
+ out = StringIO.new
15
+
16
+ Dir.mktmpdir do |dir|
17
+ source_path = File.join(dir, "people.csv")
18
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
19
+ input = [source_path, "", "", "10", "", "", "", ""].join("\n") + "\n"
20
+
21
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
22
+ stdin: StringIO.new(input),
23
+ stdout: out
24
+ ).call
25
+
26
+ assert_includes out.string, "Split complete."
27
+ assert_includes out.string, "Chunk size: 10"
28
+ assert_includes out.string, "Chunks written: 3"
29
+ assert File.file?(File.join(dir, "people_part_001.csv"))
30
+ assert File.file?(File.join(dir, "people_part_002.csv"))
31
+ assert File.file?(File.join(dir, "people_part_003.csv"))
32
+ end
33
+ end
34
+
35
+ def test_workflow_supports_tsv_separator
36
+ out = StringIO.new
37
+
38
+ Dir.mktmpdir do |dir|
39
+ source_path = File.join(dir, "people.tsv")
40
+ FileUtils.cp(fixture_path("sample_people.tsv"), source_path)
41
+ input = [source_path, "2", "", "2", "", "", "", ""].join("\n") + "\n"
42
+
43
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
44
+ stdin: StringIO.new(input),
45
+ stdout: out
46
+ ).call
47
+
48
+ chunk_path = File.join(dir, "people_part_001.tsv")
49
+ assert File.file?(chunk_path)
50
+ assert_includes File.read(chunk_path), "name\tcity"
51
+ end
52
+ end
53
+
54
+ def test_workflow_supports_headerless_mode
55
+ out = StringIO.new
56
+
57
+ Dir.mktmpdir do |dir|
58
+ source_path = File.join(dir, "people.csv")
59
+ FileUtils.cp(fixture_path("sample_people_no_headers.csv"), source_path)
60
+ input = [source_path, "", "n", "2", "", "", "", ""].join("\n") + "\n"
61
+
62
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
63
+ stdin: StringIO.new(input),
64
+ stdout: out
65
+ ).call
66
+
67
+ chunk_path = File.join(dir, "people_part_001.csv")
68
+ lines = File.read(chunk_path).lines.map(&:strip)
69
+ assert_equal "Alice,London", lines.first
70
+ assert_equal "Bob,Paris", lines.last
71
+ end
72
+ end
73
+
74
+ def test_workflow_supports_custom_separator
75
+ out = StringIO.new
76
+
77
+ Dir.mktmpdir do |dir|
78
+ source_path = File.join(dir, "people.txt")
79
+ FileUtils.cp(fixture_path("sample_people_colon.txt"), source_path)
80
+ input = [source_path, "5", ":", "", "2", "", "", "", ""].join("\n") + "\n"
81
+
82
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
83
+ stdin: StringIO.new(input),
84
+ stdout: out
85
+ ).call
86
+
87
+ chunk_path = File.join(dir, "people_part_001.txt")
88
+ assert File.file?(chunk_path)
89
+ assert_includes File.read(chunk_path), "name:city"
90
+ end
91
+ end
92
+
93
+ def test_workflow_uses_custom_output_directory_and_prefix
94
+ out = StringIO.new
95
+
96
+ Dir.mktmpdir do |dir|
97
+ source_path = File.join(dir, "people.csv")
98
+ output_dir = File.join(dir, "chunks")
99
+ Dir.mkdir(output_dir)
100
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
101
+ input = [source_path, "", "", "10", output_dir, "batch", "", ""].join("\n") + "\n"
102
+
103
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
104
+ stdin: StringIO.new(input),
105
+ stdout: out
106
+ ).call
107
+
108
+ assert File.file?(File.join(output_dir, "batch_part_001.csv"))
109
+ assert File.file?(File.join(output_dir, "batch_part_002.csv"))
110
+ assert File.file?(File.join(output_dir, "batch_part_003.csv"))
111
+ end
112
+ end
113
+
114
+ def test_workflow_does_not_overwrite_existing_file_without_confirmation
115
+ out = StringIO.new
116
+
117
+ Dir.mktmpdir do |dir|
118
+ source_path = File.join(dir, "people.csv")
119
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
120
+ existing = File.join(dir, "people_part_001.csv")
121
+ File.write(existing, "sentinel\n")
122
+ input = [source_path, "", "", "10", "", "", "", ""].join("\n") + "\n"
123
+
124
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
125
+ stdin: StringIO.new(input),
126
+ stdout: out
127
+ ).call
128
+
129
+ assert_includes out.string, "Output file already exists: #{existing}"
130
+ assert_equal "sentinel\n", File.read(existing)
131
+ end
132
+ end
133
+
134
+ def test_workflow_reports_missing_file
135
+ out = StringIO.new
136
+ input = ["/tmp/does-not-exist.csv", "", "", "10", "", "", "", ""].join("\n") + "\n"
137
+
138
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
139
+ stdin: StringIO.new(input),
140
+ stdout: out
141
+ ).call
142
+
143
+ assert_includes out.string, "File not found: /tmp/does-not-exist.csv"
144
+ end
145
+
146
+ def test_workflow_rejects_invalid_chunk_size
147
+ out = StringIO.new
148
+ input = [fixture_path("sample_people.csv"), "", "", "abc"].join("\n") + "\n"
149
+
150
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
151
+ stdin: StringIO.new(input),
152
+ stdout: out
153
+ ).call
154
+
155
+ assert_includes out.string, "Chunk size must be a positive integer."
156
+ end
157
+
158
+ def test_workflow_creates_output_directory_when_missing
159
+ out = StringIO.new
160
+
161
+ Dir.mktmpdir do |dir|
162
+ source_path = File.join(dir, "people.csv")
163
+ output_dir = File.join(dir, "chunks")
164
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
165
+ input = [source_path, "", "", "10", output_dir, "people", "", ""].join("\n") + "\n"
166
+
167
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
168
+ stdin: StringIO.new(input),
169
+ stdout: out
170
+ ).call
171
+
172
+ assert Dir.exist?(output_dir)
173
+ assert File.file?(File.join(output_dir, "people_part_001.csv"))
174
+ end
175
+ end
176
+
177
+ def test_workflow_can_write_manifest_when_enabled
178
+ out = StringIO.new
179
+
180
+ Dir.mktmpdir do |dir|
181
+ source_path = File.join(dir, "people.csv")
182
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
183
+ manifest_path = File.join(dir, "manifest.csv")
184
+ input = [source_path, "", "", "10", "", "", "", "y", manifest_path].join("\n") + "\n"
185
+
186
+ Csvtool::Interface::CLI::Workflows::RunCsvSplitWorkflow.new(
187
+ stdin: StringIO.new(input),
188
+ stdout: out
189
+ ).call
190
+
191
+ assert File.file?(manifest_path)
192
+ lines = File.read(manifest_path).lines.map(&:strip)
193
+ assert_equal "chunk_index,chunk_path,row_count", lines.first
194
+ assert_includes lines[1], ",10"
195
+ assert_includes lines[2], ",10"
196
+ assert_includes lines[3], ",5"
197
+ assert_includes out.string, "Manifest: #{manifest_path}"
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/steps/csv_split/build_session_step"
5
+
6
+ class BuildSessionStepTest < Minitest::Test
7
+ class FakeBuilder
8
+ attr_reader :params
9
+
10
+ def call(**params)
11
+ @params = params
12
+ :session
13
+ end
14
+ end
15
+
16
+ def test_builds_session_from_context
17
+ builder = FakeBuilder.new
18
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::BuildSessionStep.new
19
+ context = {
20
+ session_builder: builder,
21
+ file_path: "/tmp/data.csv",
22
+ col_sep: ",",
23
+ headers_present: true,
24
+ chunk_size: 10,
25
+ output_directory: "/tmp/out",
26
+ file_prefix: "batch",
27
+ overwrite_existing: true,
28
+ write_manifest: true,
29
+ manifest_path: "/tmp/out/manifest.csv"
30
+ }
31
+
32
+ result = step.call(context)
33
+
34
+ assert_nil result
35
+ assert_equal :session, context[:session]
36
+ assert_equal "/tmp/data.csv", builder.params[:file_path]
37
+ assert_equal true, builder.params[:write_manifest]
38
+ assert_equal "/tmp/out/manifest.csv", builder.params[:manifest_path]
39
+ end
40
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step"
5
+
6
+ class CollectInputsStepTest < Minitest::Test
7
+ class FakePrompt
8
+ def initialize(value)
9
+ @value = value
10
+ end
11
+
12
+ def call(label: nil)
13
+ @value
14
+ end
15
+ end
16
+
17
+ class FakeErrors
18
+ attr_reader :invalid_chunk_size_calls
19
+
20
+ def initialize
21
+ @invalid_chunk_size_calls = 0
22
+ end
23
+
24
+ def invalid_chunk_size
25
+ @invalid_chunk_size_calls += 1
26
+ end
27
+ end
28
+
29
+ def test_collects_inputs_into_context
30
+ errors = FakeErrors.new
31
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::CollectInputsStep.new(
32
+ file_path_prompt: FakePrompt.new("/tmp/data.csv"),
33
+ separator_prompt: FakePrompt.new(","),
34
+ headers_present_prompt: FakePrompt.new(true),
35
+ chunk_size_prompt: FakePrompt.new("10"),
36
+ errors: errors
37
+ )
38
+ context = {}
39
+
40
+ result = step.call(context)
41
+
42
+ assert_nil result
43
+ assert_equal "/tmp/data.csv", context[:file_path]
44
+ assert_equal ",", context[:col_sep]
45
+ assert_equal true, context[:headers_present]
46
+ assert_equal 10, context[:chunk_size]
47
+ end
48
+
49
+ def test_halts_on_invalid_chunk_size
50
+ errors = FakeErrors.new
51
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::CollectInputsStep.new(
52
+ file_path_prompt: FakePrompt.new("/tmp/data.csv"),
53
+ separator_prompt: FakePrompt.new(","),
54
+ headers_present_prompt: FakePrompt.new(true),
55
+ chunk_size_prompt: FakePrompt.new("abc"),
56
+ errors: errors
57
+ )
58
+
59
+ result = step.call({})
60
+
61
+ assert_equal :halt, result
62
+ assert_equal 1, errors.invalid_chunk_size_calls
63
+ end
64
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step"
5
+
6
+ class CollectManifestStepTest < Minitest::Test
7
+ class FakeSplitManifestPrompt
8
+ attr_reader :default_path
9
+
10
+ def call(default_path:)
11
+ @default_path = default_path
12
+ { write_manifest: true, manifest_path: "/tmp/out/custom_manifest.csv" }
13
+ end
14
+ end
15
+
16
+ def test_sets_manifest_values_in_context
17
+ prompt = FakeSplitManifestPrompt.new
18
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::CollectManifestStep.new(
19
+ split_manifest_prompt: prompt
20
+ )
21
+ context = { output_directory: "/tmp/out", file_prefix: "batch" }
22
+
23
+ result = step.call(context)
24
+
25
+ assert_nil result
26
+ assert_equal "/tmp/out/batch_manifest.csv", prompt.default_path
27
+ assert_equal true, context[:write_manifest]
28
+ assert_equal "/tmp/out/custom_manifest.csv", context[:manifest_path]
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_output_step"
5
+
6
+ class CollectOutputStepTest < Minitest::Test
7
+ class FakeSplitOutputPrompt
8
+ attr_reader :received
9
+
10
+ def call(default_directory:, default_prefix:)
11
+ @received = { default_directory: default_directory, default_prefix: default_prefix }
12
+ { output_directory: "/tmp/out", file_prefix: "batch", overwrite_existing: true }
13
+ end
14
+ end
15
+
16
+ def test_sets_output_values_in_context
17
+ prompt = FakeSplitOutputPrompt.new
18
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::CollectOutputStep.new(
19
+ split_output_prompt: prompt
20
+ )
21
+ context = { file_path: "/tmp/people.csv" }
22
+
23
+ result = step.call(context)
24
+
25
+ assert_nil result
26
+ assert_equal "/tmp", prompt.received[:default_directory]
27
+ assert_equal "people", prompt.received[:default_prefix]
28
+ assert_equal "/tmp/out", context[:output_directory]
29
+ assert_equal "batch", context[:file_prefix]
30
+ assert_equal true, context[:overwrite_existing]
31
+ end
32
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../../../../test_helper"
4
+ require "csvtool/interface/cli/workflows/steps/csv_split/execute_step"
5
+
6
+ class SplitExecuteStepTest < Minitest::Test
7
+ Result = Struct.new(:ok, :data) do
8
+ def ok? = ok
9
+ end
10
+
11
+ class FakeUseCase
12
+ def initialize(headers_result:, run_result:)
13
+ @headers_result = headers_result
14
+ @run_result = run_result
15
+ end
16
+
17
+ def read_headers(file_path:, col_sep:, headers_present:)
18
+ @headers_called = [file_path, col_sep, headers_present]
19
+ @headers_result
20
+ end
21
+
22
+ def call(session:)
23
+ @session = session
24
+ @run_result
25
+ end
26
+
27
+ attr_reader :headers_called, :session
28
+ end
29
+
30
+ class FakePresenter
31
+ attr_reader :summary
32
+
33
+ def print_summary(data)
34
+ @summary = data
35
+ end
36
+ end
37
+
38
+ def test_handles_header_failure
39
+ headers_fail = Result.new(false, { path: "/tmp/missing.csv" })
40
+ use_case = FakeUseCase.new(headers_result: headers_fail, run_result: Result.new(true, {}))
41
+ handled = []
42
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::ExecuteStep.new
43
+
44
+ result = step.call(
45
+ file_path: "/tmp/missing.csv",
46
+ col_sep: ",",
47
+ headers_present: true,
48
+ chunk_size: 10,
49
+ use_case: use_case,
50
+ session: Object.new,
51
+ presenter: FakePresenter.new,
52
+ handle_error: ->(r) { handled << r }
53
+ )
54
+
55
+ assert_equal :halt, result
56
+ assert_equal [headers_fail], handled
57
+ end
58
+
59
+ def test_prints_summary_on_success
60
+ use_case = FakeUseCase.new(
61
+ headers_result: Result.new(true, { headers: %w[name city] }),
62
+ run_result: Result.new(true, { chunk_count: 3, chunk_paths: ["/tmp/a.csv"], data_rows: 25 })
63
+ )
64
+ presenter = FakePresenter.new
65
+ step = Csvtool::Interface::CLI::Workflows::Steps::CsvSplit::ExecuteStep.new
66
+
67
+ result = step.call(
68
+ file_path: "/tmp/people.csv",
69
+ col_sep: ",",
70
+ headers_present: true,
71
+ chunk_size: 10,
72
+ use_case: use_case,
73
+ session: :session,
74
+ presenter: presenter,
75
+ handle_error: ->(_r) { raise "unexpected" }
76
+ )
77
+
78
+ assert_nil result
79
+ assert_equal :session, use_case.session
80
+ assert_equal 10, presenter.summary[:chunk_size]
81
+ assert_equal 3, presenter.summary[:chunk_count]
82
+ end
83
+ end
@@ -0,0 +1,26 @@
1
+ name,city
2
+ Name01,City01
3
+ Name02,City02
4
+ Name03,City03
5
+ Name04,City04
6
+ Name05,City05
7
+ Name06,City06
8
+ Name07,City07
9
+ Name08,City08
10
+ Name09,City09
11
+ Name10,City10
12
+ Name11,City11
13
+ Name12,City12
14
+ Name13,City13
15
+ Name14,City14
16
+ Name15,City15
17
+ Name16,City16
18
+ Name17,City17
19
+ Name18,City18
20
+ Name19,City19
21
+ Name20,City20
22
+ Name21,City21
23
+ Name22,City22
24
+ Name23,City23
25
+ Name24,City24
26
+ Name25,City25