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.
- checksums.yaml +4 -4
- data/README.md +51 -12
- data/docs/architecture.md +61 -4
- data/docs/release-v0.7.0-alpha.md +87 -0
- data/lib/csvtool/application/use_cases/run_csv_split.rb +97 -0
- data/lib/csvtool/cli.rb +5 -1
- data/lib/csvtool/domain/csv_split_session/split_options.rb +27 -0
- data/lib/csvtool/domain/csv_split_session/split_session.rb +20 -0
- data/lib/csvtool/domain/csv_split_session/split_source.rb +17 -0
- data/lib/csvtool/infrastructure/csv/csv_splitter.rb +64 -0
- data/lib/csvtool/infrastructure/output/csv_split_manifest_writer.rb +20 -0
- data/lib/csvtool/interface/cli/errors/presenter.rb +8 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
- data/lib/csvtool/interface/cli/prompts/chunk_size_prompt.rb +21 -0
- data/lib/csvtool/interface/cli/prompts/split_manifest_prompt.rb +30 -0
- data/lib/csvtool/interface/cli/prompts/split_output_prompt.rb +38 -0
- data/lib/csvtool/interface/cli/workflows/builders/csv_split_session_builder.rb +44 -0
- data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +26 -0
- data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +89 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/build_session_step.rb +30 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step.rb +43 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step.rb +30 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step.rb +31 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/execute_step.rb +36 -0
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/application/use_cases/run_csv_split_test.rb +124 -0
- data/test/csvtool/cli_test.rb +76 -29
- data/test/csvtool/infrastructure/csv/csv_splitter_test.rb +68 -0
- data/test/csvtool/infrastructure/output/csv_split_manifest_writer_test.rb +25 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +81 -130
- data/test/csvtool/interface/cli/prompts/chunk_size_prompt_test.rb +17 -0
- data/test/csvtool/interface/cli/prompts/split_manifest_prompt_test.rb +42 -0
- data/test/csvtool/interface/cli/prompts/split_output_prompt_test.rb +22 -0
- data/test/csvtool/interface/cli/workflows/builders/csv_split_session_builder_test.rb +30 -0
- data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +26 -0
- data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +200 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/build_session_step_test.rb +40 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step_test.rb +64 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step_test.rb +30 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step_test.rb +32 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/execute_step_test.rb +83 -0
- data/test/fixtures/split_people_25.csv +26 -0
- 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
|