csvops 0.6.0.alpha → 0.8.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -24
  3. data/docs/architecture.md +121 -4
  4. data/docs/release-v0.7.0-alpha.md +87 -0
  5. data/docs/release-v0.8.0-alpha.md +88 -0
  6. data/lib/csvtool/application/use_cases/run_csv_split.rb +97 -0
  7. data/lib/csvtool/application/use_cases/run_csv_stats.rb +64 -0
  8. data/lib/csvtool/cli.rb +9 -1
  9. data/lib/csvtool/domain/csv_split_session/split_options.rb +27 -0
  10. data/lib/csvtool/domain/csv_split_session/split_session.rb +20 -0
  11. data/lib/csvtool/domain/csv_split_session/split_source.rb +17 -0
  12. data/lib/csvtool/domain/csv_stats_session/stats_options.rb +11 -0
  13. data/lib/csvtool/domain/csv_stats_session/stats_session.rb +25 -0
  14. data/lib/csvtool/domain/csv_stats_session/stats_source.rb +17 -0
  15. data/lib/csvtool/infrastructure/csv/csv_splitter.rb +64 -0
  16. data/lib/csvtool/infrastructure/csv/csv_stats_scanner.rb +67 -0
  17. data/lib/csvtool/infrastructure/output/csv_split_manifest_writer.rb +20 -0
  18. data/lib/csvtool/infrastructure/output/csv_stats_file_writer.rb +26 -0
  19. data/lib/csvtool/interface/cli/errors/presenter.rb +8 -0
  20. data/lib/csvtool/interface/cli/menu_loop.rb +8 -2
  21. data/lib/csvtool/interface/cli/prompts/chunk_size_prompt.rb +21 -0
  22. data/lib/csvtool/interface/cli/prompts/split_manifest_prompt.rb +30 -0
  23. data/lib/csvtool/interface/cli/prompts/split_output_prompt.rb +38 -0
  24. data/lib/csvtool/interface/cli/workflows/builders/csv_split_session_builder.rb +44 -0
  25. data/lib/csvtool/interface/cli/workflows/builders/csv_stats_session_builder.rb +28 -0
  26. data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +26 -0
  27. data/lib/csvtool/interface/cli/workflows/presenters/csv_stats_presenter.rb +34 -0
  28. data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +89 -0
  29. data/lib/csvtool/interface/cli/workflows/run_csv_stats_workflow.rb +77 -0
  30. data/lib/csvtool/interface/cli/workflows/steps/csv_split/build_session_step.rb +30 -0
  31. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step.rb +43 -0
  32. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step.rb +30 -0
  33. data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step.rb +31 -0
  34. data/lib/csvtool/interface/cli/workflows/steps/csv_split/execute_step.rb +36 -0
  35. data/lib/csvtool/interface/cli/workflows/steps/csv_stats/build_session_step.rb +25 -0
  36. data/lib/csvtool/interface/cli/workflows/steps/csv_stats/collect_destination_step.rb +27 -0
  37. data/lib/csvtool/interface/cli/workflows/steps/csv_stats/collect_inputs_step.rb +31 -0
  38. data/lib/csvtool/interface/cli/workflows/steps/csv_stats/execute_step.rb +27 -0
  39. data/lib/csvtool/version.rb +1 -1
  40. data/test/csvtool/application/use_cases/run_csv_split_test.rb +124 -0
  41. data/test/csvtool/application/use_cases/run_csv_stats_test.rb +165 -0
  42. data/test/csvtool/cli_test.rb +139 -29
  43. data/test/csvtool/infrastructure/csv/csv_splitter_test.rb +68 -0
  44. data/test/csvtool/infrastructure/csv/csv_stats_scanner_test.rb +68 -0
  45. data/test/csvtool/infrastructure/output/csv_split_manifest_writer_test.rb +25 -0
  46. data/test/csvtool/infrastructure/output/csv_stats_file_writer_test.rb +38 -0
  47. data/test/csvtool/interface/cli/menu_loop_test.rb +104 -130
  48. data/test/csvtool/interface/cli/prompts/chunk_size_prompt_test.rb +17 -0
  49. data/test/csvtool/interface/cli/prompts/split_manifest_prompt_test.rb +42 -0
  50. data/test/csvtool/interface/cli/prompts/split_output_prompt_test.rb +22 -0
  51. data/test/csvtool/interface/cli/workflows/builders/csv_split_session_builder_test.rb +30 -0
  52. data/test/csvtool/interface/cli/workflows/builders/csv_stats_session_builder_test.rb +19 -0
  53. data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +26 -0
  54. data/test/csvtool/interface/cli/workflows/presenters/csv_stats_presenter_test.rb +37 -0
  55. data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +200 -0
  56. data/test/csvtool/interface/cli/workflows/run_csv_stats_workflow_test.rb +146 -0
  57. data/test/csvtool/interface/cli/workflows/steps/csv_split/build_session_step_test.rb +40 -0
  58. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step_test.rb +64 -0
  59. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step_test.rb +30 -0
  60. data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step_test.rb +32 -0
  61. data/test/csvtool/interface/cli/workflows/steps/csv_split/execute_step_test.rb +83 -0
  62. data/test/csvtool/interface/cli/workflows/steps/csv_stats/build_session_step_test.rb +36 -0
  63. data/test/csvtool/interface/cli/workflows/steps/csv_stats/collect_destination_step_test.rb +49 -0
  64. data/test/csvtool/interface/cli/workflows/steps/csv_stats/collect_inputs_step_test.rb +61 -0
  65. data/test/csvtool/interface/cli/workflows/steps/csv_stats/execute_step_test.rb +65 -0
  66. data/test/fixtures/split_people_25.csv +26 -0
  67. metadata +58 -1
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/application/use_cases/run_csv_split"
4
+ require "csvtool/interface/cli/errors/presenter"
5
+ require "csvtool/interface/cli/prompts/file_path_prompt"
6
+ require "csvtool/interface/cli/prompts/separator_prompt"
7
+ require "csvtool/interface/cli/prompts/headers_present_prompt"
8
+ require "csvtool/interface/cli/prompts/chunk_size_prompt"
9
+ require "csvtool/interface/cli/prompts/yes_no_prompt"
10
+ require "csvtool/interface/cli/prompts/split_output_prompt"
11
+ require "csvtool/interface/cli/prompts/split_manifest_prompt"
12
+ require "csvtool/interface/cli/workflows/builders/csv_split_session_builder"
13
+ require "csvtool/interface/cli/workflows/presenters/csv_split_presenter"
14
+ require "csvtool/interface/cli/workflows/support/result_error_handler"
15
+ require "csvtool/interface/cli/workflows/steps/workflow_step_pipeline"
16
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step"
17
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_output_step"
18
+ require "csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step"
19
+ require "csvtool/interface/cli/workflows/steps/csv_split/build_session_step"
20
+ require "csvtool/interface/cli/workflows/steps/csv_split/execute_step"
21
+
22
+ module Csvtool
23
+ module Interface
24
+ module CLI
25
+ module Workflows
26
+ class RunCsvSplitWorkflow
27
+ def initialize(stdin:, stdout:, use_case: Application::UseCases::RunCsvSplit.new)
28
+ @stdin = stdin
29
+ @stdout = stdout
30
+ @use_case = use_case
31
+ @errors = Interface::CLI::Errors::Presenter.new(stdout: stdout)
32
+ @session_builder = Builders::CsvSplitSessionBuilder.new
33
+ @presenter = Presenters::CsvSplitPresenter.new(stdout: stdout)
34
+ @result_error_handler = Support::ResultErrorHandler.new(errors: @errors)
35
+ end
36
+
37
+ def call
38
+ context = {
39
+ use_case: @use_case,
40
+ session_builder: @session_builder,
41
+ presenter: @presenter,
42
+ handle_error: method(:handle_error)
43
+ }
44
+ pipeline = Steps::WorkflowStepPipeline.new(steps: [
45
+ Steps::CsvSplit::CollectInputsStep.new(
46
+ file_path_prompt: Interface::CLI::Prompts::FilePathPrompt.new(stdin: @stdin, stdout: @stdout),
47
+ separator_prompt: Interface::CLI::Prompts::SeparatorPrompt.new(stdin: @stdin, stdout: @stdout, errors: @errors),
48
+ headers_present_prompt: Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: @stdin, stdout: @stdout),
49
+ chunk_size_prompt: Interface::CLI::Prompts::ChunkSizePrompt.new(stdin: @stdin, stdout: @stdout),
50
+ errors: @errors
51
+ ),
52
+ Steps::CsvSplit::CollectOutputStep.new(
53
+ split_output_prompt: Interface::CLI::Prompts::SplitOutputPrompt.new(
54
+ stdin: @stdin,
55
+ stdout: @stdout,
56
+ yes_no_prompt: Interface::CLI::Prompts::YesNoPrompt.new(stdin: @stdin, stdout: @stdout)
57
+ )
58
+ ),
59
+ Steps::CsvSplit::CollectManifestStep.new(
60
+ split_manifest_prompt: Interface::CLI::Prompts::SplitManifestPrompt.new(
61
+ stdin: @stdin,
62
+ stdout: @stdout,
63
+ yes_no_prompt: Interface::CLI::Prompts::YesNoPrompt.new(stdin: @stdin, stdout: @stdout)
64
+ )
65
+ ),
66
+ Steps::CsvSplit::BuildSessionStep.new,
67
+ Steps::CsvSplit::ExecuteStep.new
68
+ ])
69
+ pipeline.call(context)
70
+ nil
71
+ end
72
+
73
+ private
74
+
75
+ def handle_error(result)
76
+ @result_error_handler.call(result, {
77
+ file_not_found: ->(r, errors) { errors.file_not_found(r.data[:path]) },
78
+ no_headers: ->(_r, errors) { errors.no_headers },
79
+ could_not_parse_csv: ->(_r, errors) { errors.could_not_parse_csv },
80
+ cannot_read_file: ->(r, errors) { errors.cannot_read_file(r.data[:path]) },
81
+ cannot_write_output_file: ->(r, errors) { errors.cannot_write_output_file(r.data[:path], r.data[:error_class]) },
82
+ output_file_exists: ->(r, errors) { errors.output_file_exists(r.data[:path]) }
83
+ })
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/application/use_cases/run_csv_stats"
4
+ require "csvtool/interface/cli/errors/presenter"
5
+ require "csvtool/interface/cli/prompts/file_path_prompt"
6
+ require "csvtool/interface/cli/prompts/separator_prompt"
7
+ require "csvtool/interface/cli/prompts/headers_present_prompt"
8
+ require "csvtool/interface/cli/prompts/output_destination_prompt"
9
+ require "csvtool/interface/cli/workflows/builders/csv_stats_session_builder"
10
+ require "csvtool/interface/cli/workflows/presenters/csv_stats_presenter"
11
+ require "csvtool/interface/cli/workflows/support/result_error_handler"
12
+ require "csvtool/interface/cli/workflows/support/output_destination_mapper"
13
+ require "csvtool/interface/cli/workflows/steps/workflow_step_pipeline"
14
+ require "csvtool/interface/cli/workflows/steps/csv_stats/collect_inputs_step"
15
+ require "csvtool/interface/cli/workflows/steps/csv_stats/collect_destination_step"
16
+ require "csvtool/interface/cli/workflows/steps/csv_stats/build_session_step"
17
+ require "csvtool/interface/cli/workflows/steps/csv_stats/execute_step"
18
+
19
+ module Csvtool
20
+ module Interface
21
+ module CLI
22
+ module Workflows
23
+ class RunCsvStatsWorkflow
24
+ def initialize(stdin:, stdout:, use_case: Application::UseCases::RunCsvStats.new)
25
+ @stdin = stdin
26
+ @stdout = stdout
27
+ @use_case = use_case
28
+ @errors = Interface::CLI::Errors::Presenter.new(stdout: stdout)
29
+ @session_builder = Builders::CsvStatsSessionBuilder.new
30
+ @presenter = Presenters::CsvStatsPresenter.new(stdout: stdout)
31
+ @output_destination_mapper = Support::OutputDestinationMapper.new
32
+ @result_error_handler = Support::ResultErrorHandler.new(errors: @errors)
33
+ end
34
+
35
+ def call
36
+ context = {
37
+ use_case: @use_case,
38
+ session_builder: @session_builder,
39
+ output_destination_mapper: @output_destination_mapper,
40
+ presenter: @presenter,
41
+ handle_error: method(:handle_error)
42
+ }
43
+ pipeline = Steps::WorkflowStepPipeline.new(steps: [
44
+ Steps::CsvStats::CollectInputsStep.new(
45
+ file_path_prompt: Interface::CLI::Prompts::FilePathPrompt.new(stdin: @stdin, stdout: @stdout),
46
+ separator_prompt: Interface::CLI::Prompts::SeparatorPrompt.new(stdin: @stdin, stdout: @stdout, errors: @errors),
47
+ headers_present_prompt: Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: @stdin, stdout: @stdout)
48
+ ),
49
+ Steps::CsvStats::CollectDestinationStep.new(
50
+ output_destination_prompt: Interface::CLI::Prompts::OutputDestinationPrompt.new(
51
+ stdin: @stdin,
52
+ stdout: @stdout,
53
+ errors: @errors
54
+ )
55
+ ),
56
+ Steps::CsvStats::BuildSessionStep.new,
57
+ Steps::CsvStats::ExecuteStep.new
58
+ ])
59
+ pipeline.call(context)
60
+ nil
61
+ end
62
+
63
+ private
64
+
65
+ def handle_error(result)
66
+ @result_error_handler.call(result, {
67
+ file_not_found: ->(r, errors) { errors.file_not_found(r.data[:path]) },
68
+ could_not_parse_csv: ->(_r, errors) { errors.could_not_parse_csv },
69
+ cannot_read_file: ->(r, errors) { errors.cannot_read_file(r.data[:path]) },
70
+ cannot_write_output_file: ->(r, errors) { errors.cannot_write_output_file(r.data[:path], r.data[:error_class]) }
71
+ })
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvSplit
9
+ class BuildSessionStep
10
+ def call(context)
11
+ context[:session] = context.fetch(:session_builder).call(
12
+ file_path: context.fetch(:file_path),
13
+ col_sep: context.fetch(:col_sep),
14
+ headers_present: context.fetch(:headers_present),
15
+ chunk_size: context.fetch(:chunk_size),
16
+ output_directory: context[:output_directory],
17
+ file_prefix: context[:file_prefix],
18
+ overwrite_existing: context.fetch(:overwrite_existing, false),
19
+ write_manifest: context.fetch(:write_manifest, false),
20
+ manifest_path: context[:manifest_path]
21
+ )
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvSplit
9
+ class CollectInputsStep
10
+ def initialize(file_path_prompt:, separator_prompt:, headers_present_prompt:, chunk_size_prompt:, errors:)
11
+ @file_path_prompt = file_path_prompt
12
+ @separator_prompt = separator_prompt
13
+ @headers_present_prompt = headers_present_prompt
14
+ @chunk_size_prompt = chunk_size_prompt
15
+ @errors = errors
16
+ end
17
+
18
+ def call(context)
19
+ context[:file_path] = @file_path_prompt.call(label: "Source CSV file path: ")
20
+ col_sep = @separator_prompt.call
21
+ return :halt if col_sep.nil?
22
+
23
+ context[:col_sep] = col_sep
24
+ context[:headers_present] = @headers_present_prompt.call
25
+ chunk_size = Integer(@chunk_size_prompt.call)
26
+ if chunk_size <= 0
27
+ @errors.invalid_chunk_size
28
+ return :halt
29
+ end
30
+
31
+ context[:chunk_size] = chunk_size
32
+ nil
33
+ rescue ArgumentError, TypeError
34
+ @errors.invalid_chunk_size
35
+ :halt
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvSplit
9
+ class CollectManifestStep
10
+ def initialize(split_manifest_prompt:)
11
+ @split_manifest_prompt = split_manifest_prompt
12
+ end
13
+
14
+ def call(context)
15
+ default_path = File.join(
16
+ context.fetch(:output_directory),
17
+ "#{context.fetch(:file_prefix)}_manifest.csv"
18
+ )
19
+ manifest = @split_manifest_prompt.call(default_path: default_path)
20
+ context[:write_manifest] = manifest[:write_manifest]
21
+ context[:manifest_path] = manifest[:manifest_path]
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvSplit
9
+ class CollectOutputStep
10
+ def initialize(split_output_prompt:)
11
+ @split_output_prompt = split_output_prompt
12
+ end
13
+
14
+ def call(context)
15
+ file_path = context.fetch(:file_path)
16
+ output = @split_output_prompt.call(
17
+ default_directory: File.dirname(file_path),
18
+ default_prefix: File.basename(file_path, ".*")
19
+ )
20
+ context[:output_directory] = output[:output_directory]
21
+ context[:file_prefix] = output[:file_prefix]
22
+ context[:overwrite_existing] = output[:overwrite_existing]
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvSplit
9
+ class ExecuteStep
10
+ def call(context)
11
+ headers_result = context.fetch(:use_case).read_headers(
12
+ file_path: context.fetch(:file_path),
13
+ col_sep: context.fetch(:col_sep),
14
+ headers_present: context.fetch(:headers_present)
15
+ )
16
+ unless headers_result.ok?
17
+ context.fetch(:handle_error).call(headers_result)
18
+ return :halt
19
+ end
20
+
21
+ result = context.fetch(:use_case).call(session: context.fetch(:session))
22
+ unless result.ok?
23
+ context.fetch(:handle_error).call(result)
24
+ return :halt
25
+ end
26
+
27
+ context.fetch(:presenter).print_summary(result.data.merge(chunk_size: context.fetch(:chunk_size)))
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvStats
9
+ class BuildSessionStep
10
+ def call(context)
11
+ context[:session] = context.fetch(:session_builder).call(
12
+ file_path: context.fetch(:file_path),
13
+ col_sep: context.fetch(:col_sep),
14
+ headers_present: context.fetch(:headers_present),
15
+ destination: context.fetch(:output_destination)
16
+ )
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvStats
9
+ class CollectDestinationStep
10
+ def initialize(output_destination_prompt:)
11
+ @output_destination_prompt = output_destination_prompt
12
+ end
13
+
14
+ def call(context)
15
+ output_destination = @output_destination_prompt.call
16
+ return :halt if output_destination.nil?
17
+
18
+ context[:output_destination] = context.fetch(:output_destination_mapper).call(output_destination)
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvStats
9
+ class CollectInputsStep
10
+ def initialize(file_path_prompt:, separator_prompt:, headers_present_prompt:)
11
+ @file_path_prompt = file_path_prompt
12
+ @separator_prompt = separator_prompt
13
+ @headers_present_prompt = headers_present_prompt
14
+ end
15
+
16
+ def call(context)
17
+ context[:file_path] = @file_path_prompt.call(label: "CSV file path: ")
18
+ col_sep = @separator_prompt.call
19
+ return :halt if col_sep.nil?
20
+
21
+ context[:col_sep] = col_sep
22
+ context[:headers_present] = @headers_present_prompt.call
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CsvStats
9
+ class ExecuteStep
10
+ def call(context)
11
+ result = context.fetch(:use_case).call(session: context.fetch(:session))
12
+ unless result.ok?
13
+ context.fetch(:handle_error).call(result)
14
+ return :halt
15
+ end
16
+
17
+ context.fetch(:presenter).print_summary(result.data)
18
+ context.fetch(:presenter).print_file_written(result.data[:output_path]) if result.data[:output_path]
19
+ nil
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Csvtool
4
- VERSION = "0.6.0.alpha"
4
+ VERSION = "0.8.0.alpha"
5
5
  end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/application/use_cases/run_csv_split"
5
+ require "csvtool/domain/csv_split_session/split_source"
6
+ require "csvtool/domain/csv_split_session/split_options"
7
+ require "csvtool/domain/csv_split_session/split_session"
8
+ require "tmpdir"
9
+ require "fileutils"
10
+
11
+ class RunCsvSplitTest < Minitest::Test
12
+ def fixture_path(name)
13
+ File.expand_path("../../../fixtures/#{name}", __dir__)
14
+ end
15
+
16
+ def test_splits_25_rows_into_10_10_5_with_headers
17
+ use_case = Csvtool::Application::UseCases::RunCsvSplit.new
18
+
19
+ Dir.mktmpdir do |dir|
20
+ source_path = File.join(dir, "people.csv")
21
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
22
+
23
+ source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
24
+ options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(chunk_size: 10)
25
+ session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
26
+
27
+ result = use_case.call(session: session)
28
+
29
+ assert result.ok?
30
+ assert_equal 3, result.data[:chunk_count]
31
+ assert_equal 25, result.data[:data_rows]
32
+ assert_equal 3, result.data[:chunk_paths].length
33
+
34
+ chunk_1 = File.read(result.data[:chunk_paths][0]).lines.map(&:strip)
35
+ chunk_2 = File.read(result.data[:chunk_paths][1]).lines.map(&:strip)
36
+ chunk_3 = File.read(result.data[:chunk_paths][2]).lines.map(&:strip)
37
+
38
+ assert_equal 11, chunk_1.length
39
+ assert_equal 11, chunk_2.length
40
+ assert_equal 6, chunk_3.length
41
+ assert_equal "name,city", chunk_1.first
42
+ assert_equal "name,city", chunk_2.first
43
+ assert_equal "name,city", chunk_3.first
44
+ assert_equal "Name01,City01", chunk_1[1]
45
+ assert_equal "Name10,City10", chunk_1[10]
46
+ assert_equal "Name11,City11", chunk_2[1]
47
+ assert_equal "Name20,City20", chunk_2[10]
48
+ assert_equal "Name21,City21", chunk_3[1]
49
+ assert_equal "Name25,City25", chunk_3[5]
50
+ end
51
+ end
52
+
53
+ def test_returns_output_file_exists_when_overwrite_is_disabled
54
+ use_case = Csvtool::Application::UseCases::RunCsvSplit.new
55
+
56
+ Dir.mktmpdir do |dir|
57
+ source_path = File.join(dir, "people.csv")
58
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
59
+ File.write(File.join(dir, "people_part_001.csv"), "sentinel\n")
60
+
61
+ source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
62
+ options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(chunk_size: 10, overwrite_existing: false)
63
+ session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
64
+
65
+ result = use_case.call(session: session)
66
+
67
+ refute result.ok?
68
+ assert_equal :output_file_exists, result.error
69
+ assert_equal File.join(dir, "people_part_001.csv"), result.data[:path]
70
+ end
71
+ end
72
+
73
+ def test_creates_output_directory_when_it_does_not_exist
74
+ use_case = Csvtool::Application::UseCases::RunCsvSplit.new
75
+
76
+ Dir.mktmpdir do |dir|
77
+ source_path = File.join(dir, "people.csv")
78
+ output_dir = File.join(dir, "new_chunks")
79
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
80
+
81
+ source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
82
+ options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(
83
+ chunk_size: 10,
84
+ output_directory: output_dir,
85
+ file_prefix: "batch"
86
+ )
87
+ session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
88
+
89
+ result = use_case.call(session: session)
90
+
91
+ assert result.ok?
92
+ assert Dir.exist?(output_dir)
93
+ assert File.file?(File.join(output_dir, "batch_part_001.csv"))
94
+ end
95
+ end
96
+
97
+ def test_writes_manifest_when_enabled
98
+ use_case = Csvtool::Application::UseCases::RunCsvSplit.new
99
+
100
+ Dir.mktmpdir do |dir|
101
+ source_path = File.join(dir, "people.csv")
102
+ manifest_path = File.join(dir, "manifest.csv")
103
+ FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
104
+
105
+ source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
106
+ options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(
107
+ chunk_size: 10,
108
+ write_manifest: true,
109
+ manifest_path: manifest_path
110
+ )
111
+ session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
112
+
113
+ result = use_case.call(session: session)
114
+
115
+ assert result.ok?
116
+ assert_equal manifest_path, result.data[:manifest_path]
117
+ lines = File.read(manifest_path).lines.map(&:strip)
118
+ assert_equal "chunk_index,chunk_path,row_count", lines.first
119
+ assert_includes lines[1], ",10"
120
+ assert_includes lines[2], ",10"
121
+ assert_includes lines[3], ",5"
122
+ end
123
+ end
124
+ end