ticket-replicator 0.1.1

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/Guardfile +158 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +136 -0
  9. data/Rakefile +23 -0
  10. data/bin/ticket-replicator +67 -0
  11. data/config/examples/ticket-replicator.mappings.yml +54 -0
  12. data/cucumber.yml +7 -0
  13. data/features/extract-sap-solution-manager-defect-tickets.feature +45 -0
  14. data/features/load_tickets_in_jira.feature +129 -0
  15. data/features/setup_ticket_replicator.feature +85 -0
  16. data/features/step_definitions/anonymized_sample.xlsx +0 -0
  17. data/features/step_definitions/anonymized_sample.xlsx:Zone.Identifier +3 -0
  18. data/features/step_definitions/execution_context_steps.rb +13 -0
  19. data/features/step_definitions/extract_defect_tickets_from_sap_solution_manager_steps.rb.rb +29 -0
  20. data/features/step_definitions/load_tickets_in_jira_steps.rb +47 -0
  21. data/features/step_definitions/transform_solution_manager_tickets_steps.rb +21 -0
  22. data/features/support/10.setup_cucumber.rb +10 -0
  23. data/features/support/env.rb +15 -0
  24. data/features/support/hooks.rb +13 -0
  25. data/features/support/manage_mock_sap_solution_manager.rb.DISABLED +12 -0
  26. data/features/support/mocks/mock_defect_ticket_server.rb.DISABLED +251 -0
  27. data/features/support/setup_rspec.rb +15 -0
  28. data/features/support/setup_simplecov.rb +5 -0
  29. data/features/transform-solution-manager-tickets-into-jira-loadable-tickets.feature +313 -0
  30. data/features/transform_and_load_extracted_ticket_queue.feature +121 -0
  31. data/lib/tasks/version.rake +55 -0
  32. data/lib/ticket/replicator/defect_export_automation.rb.DISABLED +128 -0
  33. data/lib/ticket/replicator/file_loader.rb +46 -0
  34. data/lib/ticket/replicator/file_replicator.rb +67 -0
  35. data/lib/ticket/replicator/file_transformer/for_csv.rb +22 -0
  36. data/lib/ticket/replicator/file_transformer/for_xlsx.rb +34 -0
  37. data/lib/ticket/replicator/file_transformer.rb +70 -0
  38. data/lib/ticket/replicator/jira_project.rb +65 -0
  39. data/lib/ticket/replicator/replicated_summary.rb +73 -0
  40. data/lib/ticket/replicator/row_loader.rb +109 -0
  41. data/lib/ticket/replicator/row_transformer.rb +126 -0
  42. data/lib/ticket/replicator/s_a_p_solution_manager_client.rb.DISABLED +169 -0
  43. data/lib/ticket/replicator/setup.rb +49 -0
  44. data/lib/ticket/replicator/ticket.rb +70 -0
  45. data/lib/ticket/replicator/ticket_status_transitioner.rb +45 -0
  46. data/lib/ticket/replicator/version.rb +7 -0
  47. data/lib/ticket/replicator.rb +90 -0
  48. data/sig/ticket/replicator.rbs +6 -0
  49. data/spec/spec_helper.rb +19 -0
  50. data/spec/ticket/replicator/file_loader_spec.rb +77 -0
  51. data/spec/ticket/replicator/file_replicator_spec.rb +153 -0
  52. data/spec/ticket/replicator/file_transformer/for_csv_spec.rb +52 -0
  53. data/spec/ticket/replicator/file_transformer/for_xlsx_spec.rb +52 -0
  54. data/spec/ticket/replicator/file_transformer_spec.rb +83 -0
  55. data/spec/ticket/replicator/jira_project_spec.rb +127 -0
  56. data/spec/ticket/replicator/replicated_summary_spec.rb +70 -0
  57. data/spec/ticket/replicator/row_loader_spec.rb +245 -0
  58. data/spec/ticket/replicator/row_transformer_spec.rb +234 -0
  59. data/spec/ticket/replicator/setup_spec.rb +80 -0
  60. data/spec/ticket/replicator/ticket_spec.rb +244 -0
  61. data/spec/ticket/replicator/ticket_status_transitioner_spec.rb +123 -0
  62. data/spec/ticket/replicator_spec.rb +137 -0
  63. data/transformed_file1 +1 -0
  64. metadata +235 -0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ticket/replicator/file_loader"
4
+
5
+ module Ticket
6
+ class Replicator
7
+ class FileLoader
8
+ RSpec.describe FileLoader do
9
+ let(:loader) { described_class.send(:new, jira_project, "file.csv") }
10
+ let(:jira_project) { instance_double(JiraProject) }
11
+ let(:logger) { instance_double(Logger, info: nil) }
12
+
13
+ describe ".run_on" do
14
+ it "loads the given file" do
15
+ expect(described_class).to receive(:new).with(jira_project, "file.csv").and_return(loader)
16
+ expect(described_class).to receive(:log).and_return(logger)
17
+ expect(loader).to receive(:run)
18
+
19
+ described_class.run_on(jira_project, "file.csv")
20
+ end
21
+ end
22
+
23
+ describe "#run" do
24
+ let(:rows) { %i[a_row another_row] }
25
+ before do
26
+ expect(loader).to receive(:rows).and_return(rows)
27
+ end
28
+
29
+ it "loads each rows" do
30
+ rows.each { |row| expect(RowLoader).to receive(:run_on).with(jira_project, row) }
31
+
32
+ loader.run
33
+ end
34
+
35
+ context "when a row loading error occurs" do
36
+ before do
37
+ allow(RowLoader).to receive(:run_on).with(jira_project, :a_row)
38
+ allow(RowLoader).to receive(:run_on).with(jira_project, :another_row)
39
+ .and_raise(LoadError, "error")
40
+ end
41
+
42
+ it "raises an error message including the file name and line number of the row that was processed" do
43
+ expect { loader.run }.to raise_error(LoadError, <<~EOEXPECTEDERRORMSG)
44
+ file.csv:3: error while loading row:
45
+ error:
46
+ :another_row
47
+ EOEXPECTEDERRORMSG
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#rows" do
53
+ let(:csv) do
54
+ <<~EOCSV
55
+ ID,Status,Priority,Team,Summary
56
+ 123,Open,Low,A Team,summary
57
+ 456,Open,Low,Another Team,summary
58
+ EOCSV
59
+ end
60
+
61
+ it "reads rows from the CSV" do
62
+ expect(CSV).to receive(:read).with("file.csv", headers: true, header_converters: :symbol)
63
+ .and_call_original
64
+
65
+ expect(IO).to receive(:open).and_return(csv)
66
+
67
+ expect(loader.rows.each.to_a.collect(&:to_h))
68
+ .to eq([
69
+ { id: "123", status: "Open", priority: "Low", team: "A Team", summary: "summary" },
70
+ { id: "456", status: "Open", priority: "Low", team: "Another Team", summary: "summary" }
71
+ ])
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ticket
4
+ class Replicator
5
+ # rubocop:disable Metrics/ClassLength
6
+ class FileReplicator
7
+ RSpec.describe FileReplicator do
8
+ let(:file_replicator) { described_class.send(:new, replicator, "extracted.csv") }
9
+ let(:replicator) { instance_double(Replicator) }
10
+
11
+ describe ".run_on" do
12
+ it "replicates the given file" do
13
+ expect(described_class).to receive(:new).with(replicator, "file.csv").and_return(file_replicator)
14
+ expect(file_replicator).to receive(:run)
15
+
16
+ described_class.run_on(replicator, "file.csv")
17
+ end
18
+ end
19
+
20
+ describe ".transform" do
21
+ it "transforms the given file" do
22
+ expect(described_class).to receive(:new).with(replicator, "extracted.csv").and_return(file_replicator)
23
+ expect(file_replicator).to receive(:transform)
24
+
25
+ described_class.transform(replicator, "extracted.csv")
26
+ end
27
+ end
28
+ describe ".load" do
29
+ it "loads the given file" do
30
+ expect(described_class).to receive(:load).with(replicator, "transformed.csv")
31
+
32
+ described_class.load(replicator, "transformed.csv")
33
+ end
34
+ end
35
+
36
+ describe "#run" do
37
+ it "replicates the given file content" do
38
+ expect(file_replicator).to receive(:transform).ordered
39
+ expect(file_replicator).to receive(:load).ordered
40
+ expect(file_replicator).to receive(:delete_transformed_file).ordered
41
+ expect(file_replicator).to receive(:archive_extracted_file).ordered
42
+
43
+ file_replicator.run
44
+ end
45
+
46
+ context "when the transform fails" do
47
+ it "does not archive the extracted file" do
48
+ expect(file_replicator)
49
+ .to receive(:transform).and_raise(FileTransformer::TransformError, "error while transforming:")
50
+
51
+ expect(file_replicator).not_to receive(:load)
52
+ expect(file_replicator).not_to receive(:archive_extracted_file)
53
+
54
+ expect { file_replicator.run }
55
+ .to raise_error(FileTransformer::TransformError, "error while transforming:")
56
+ end
57
+ end
58
+
59
+ context "when the load fails" do
60
+ it "does not archive the extracted file" do
61
+ expect(file_replicator).to receive(:transform)
62
+ expect(file_replicator).to receive(:load).and_raise(FileLoader::LoadError, "error while loading:")
63
+ expect(file_replicator).not_to receive(:archive_extracted_file)
64
+
65
+ expect { file_replicator.run }.to raise_error(FileLoader::LoadError, "error while loading:")
66
+ end
67
+ end
68
+ end
69
+
70
+ describe "#transform" do
71
+ it "transforms the given file" do
72
+ expect(file_replicator).to receive(:transformed_path).and_return("transformed.csv")
73
+ expect(FileTransformer).to receive(:run_on).with("extracted.csv", "transformed.csv")
74
+
75
+ file_replicator.transform
76
+ end
77
+ end
78
+
79
+ describe "#load" do
80
+ it "loads the transformed file" do
81
+ expect(file_replicator).to receive(:transformed_path).and_return("transformed.csv")
82
+ expect(replicator).to receive(:jira_project).and_return(:jira_project)
83
+ expect(FileLoader).to receive(:run_on).with(:jira_project, "transformed.csv")
84
+
85
+ file_replicator.load
86
+ end
87
+ end
88
+
89
+ describe "#delete_transformed_file" do
90
+ it "deletes the transformed file" do
91
+ expect(file_replicator).to receive(:transformed_path).and_return("transformed.csv")
92
+ expect(FileUtils).to receive(:rm).with("transformed.csv")
93
+
94
+ file_replicator.delete_transformed_file
95
+ end
96
+ end
97
+
98
+ describe "#archive_extracted_file" do
99
+ it "archives the extracted file" do
100
+ expect(file_replicator)
101
+ .to receive(:archived_path).and_return("path/to/queue/archived/file")
102
+
103
+ expect(FileUtils).to receive(:mv).with("extracted.csv", "path/to/queue/archived/file")
104
+
105
+ file_replicator.archive_extracted_file
106
+ end
107
+ end
108
+
109
+ describe "#transformed_pathr" do
110
+ before do
111
+ allow(replicator).to receive_messages(extracted_folder: "queue/extracted")
112
+ allow(replicator).to receive_messages(transformed_folder: "queue/transformed")
113
+ end
114
+
115
+ context "when the extracted file was a CSV" do
116
+ let(:file_replicator) { described_class.send(:new, replicator, "queue/extracted/file.csv") }
117
+
118
+ it "returns the transformed path for the given file" do
119
+ expect(file_replicator.transformed_path).to eq("queue/transformed/file.csv")
120
+ end
121
+ end
122
+
123
+ context "when the extracted file was a XLSX" do
124
+ let(:file_replicator) { described_class.send(:new, replicator, "queue/extracted/file.xlsx") }
125
+
126
+ it "returns the transformed path for the given file" do
127
+ expect(file_replicator.transformed_path).to eq("queue/transformed/file.csv")
128
+ end
129
+ end
130
+ end
131
+
132
+ describe "#archived_path" do
133
+ it "returns the archived path for the given file" do
134
+ allow(replicator).to receive_messages(archived_folder: "queue/archived")
135
+ allow(file_replicator).to receive_messages(timestamp: "<timestamp>")
136
+
137
+ expect(file_replicator.archived_path).to eq("queue/archived/<timestamp>.extracted.csv")
138
+ end
139
+ end
140
+
141
+ describe "#timestamp" do
142
+ it "returns the current timestamp" do
143
+ expect(Jira::Auto::Tool::Helpers::OverridableTime)
144
+ .to receive(:now).and_return(Time.new(2025, 5, 9, 21, 8, 0))
145
+
146
+ expect(file_replicator.send(:timestamp)).to eq("2025-05-09.21h08m00")
147
+ end
148
+ end
149
+ end
150
+ end
151
+ # rubocop:enable Metrics/ClassLength
152
+ end
153
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ module Ticket
6
+ class Replicator
7
+ class FileTransformer
8
+ class ForCSV
9
+ RSpec.describe ForCSV do
10
+ let(:transformer) { described_class.send(:new, "source.csv", "transformed.csv") }
11
+
12
+ describe "#extracted_rows" do
13
+ let(:extracted_csv) do
14
+ <<~EOCSV
15
+ ID,Status,Priority,Team,Summary
16
+ 123,Open,4 - Low,Source Team,summary
17
+ 456,Open,4 - Low,Source Team,summary
18
+ EOCSV
19
+ end
20
+
21
+ it "reads rows from the CSV" do
22
+ expect(CSV).to receive(:read).with("source.csv", headers: true, header_converters: :downcase)
23
+ .and_call_original
24
+
25
+ expect(IO).to receive(:open).and_return(extracted_csv)
26
+
27
+ expect(transformer.extracted_rows.each.to_a.collect(&:to_h))
28
+ .to eq([
29
+ { "id" => "123", "priority" => "4 - Low", "status" => "Open",
30
+ "summary" => "summary", "team" => "Source Team" },
31
+ { "id" => "456", "priority" => "4 - Low", "status" => "Open",
32
+ "summary" => "summary", "team" => "Source Team" }
33
+ ])
34
+ end
35
+
36
+ context "when reading the CSV generates an error" do
37
+ it "raises an error message including the file name and line number of the row that was processed" do
38
+ expect(CSV).to receive(:read).with("source.csv", headers: true, header_converters: :downcase)
39
+ .and_raise(RuntimeError, "parser error")
40
+
41
+ expect { transformer.extracted_rows }.to raise_error(CSVReaderError, <<~EOEXPECTEDERRORMSG)
42
+ source.csv:1: error while reading CSV file:
43
+ parser error
44
+ EOEXPECTEDERRORMSG
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ require "ticket/replicator/file_transformer/for_xlsx"
6
+
7
+ module Ticket
8
+ class Replicator
9
+ class FileTransformer
10
+ class ForXLSX
11
+ RSpec.describe ForXLSX do
12
+ let(:transformer) { described_class.send(:new, "extracted.xlsx", "transformed.csv") }
13
+
14
+ describe "#extracted_rows" do
15
+ let(:column_names) { ("A".."E").to_a }
16
+ let(:actual_parsed_xlsx_data) do
17
+ [
18
+ ["Defect", "status", "priority", "team", "Defect (2)"],
19
+ ["123", "Open", "4 - Low", "Source Team", "summary"],
20
+ ["456", "Open", "4 - Low", "Source Team", "summary"]
21
+ ].collect { |values| column_names.zip(values).to_h }
22
+ end
23
+
24
+ let(:xlsx_content) { "xlsx content" }
25
+
26
+ let(:workbook) { instance_double(Creek::Book, sheets: [extracted_data_sheet]) }
27
+ let(:extracted_data_sheet) { instance_double(Creek::Sheet, simple_rows: actual_parsed_xlsx_data) }
28
+
29
+ it "reads rows from the XLSX" do
30
+ expect(Creek::Book).to receive(:new).with("extracted.xlsx", { :convert_numerics => false })
31
+ .and_return(workbook)
32
+
33
+ expect(transformer.extracted_rows)
34
+ end
35
+
36
+ context "when reading the XLSX generates an error" do
37
+ it "raises an error message including the file name" do
38
+ expect(Creek::Book).to receive(:new).with("extracted.xlsx", { :convert_numerics => false })
39
+ .and_raise(RuntimeError, "parser error")
40
+
41
+ expect { transformer.extracted_rows }.to raise_error(XLSXReaderError, <<~EOEXPECTEDERRORMSG)
42
+ extracted.xlsx:1: error while reading XLSX file:
43
+ parser error
44
+ EOEXPECTEDERRORMSG
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ticket/replicator/file_transformer"
4
+
5
+ module Ticket
6
+ class Replicator
7
+ class FileTransformer
8
+ RSpec.describe FileTransformer do
9
+ let(:transformer) { described_class.send(:new, "source.csv", "transformed.csv") }
10
+
11
+ describe ".run_on" do
12
+ it "transforms the given file" do
13
+ expect(described_class).to receive(:run_class).with("file.csv").and_return(described_class)
14
+ expect(described_class).to receive(:new).with("file.csv", "transformed.csv").and_return(transformer)
15
+ expect(transformer).to receive(:run)
16
+
17
+ described_class.run_on("file.csv", "transformed.csv")
18
+ end
19
+ end
20
+
21
+ describe ".run_class" do
22
+ it { expect(described_class.run_class("foo_bar.csv")).to eq(FileTransformer::ForCSV) }
23
+ it { expect(described_class.run_class("foo_bar.xlsx")).to eq(FileTransformer::ForXLSX) }
24
+ end
25
+
26
+ describe "#run" do
27
+ let(:transformed_csv_writer) { double("csv") }
28
+
29
+ it "transforms the given file" do
30
+ expect(CSV).to receive(:open).with("transformed.csv", "wb", force_quotes: true)
31
+ .and_yield(transformed_csv_writer)
32
+
33
+ allow(transformer).to receive_messages(transformed_headers: :a_header)
34
+ allow(transformer).to receive_messages(transformed_rows: %i[a_row another_row])
35
+
36
+ expect(transformed_csv_writer).to receive(:<<).with(:a_header)
37
+ expect(transformed_csv_writer).to receive(:<<).with(:a_row)
38
+ expect(transformed_csv_writer).to receive(:<<).with(:another_row)
39
+
40
+ transformer.run
41
+ end
42
+ end
43
+
44
+ describe "#transformed_headers" do
45
+ it "returns the headers" do
46
+ expect(transformer.transformed_headers).to eq(%w[ID Status Resolution Priority Team Summary])
47
+ end
48
+ end
49
+
50
+ describe "#transformed_rows" do
51
+ before do
52
+ allow(transformer).to receive_messages(extracted_rows: %i[a_row another_row])
53
+ end
54
+
55
+ it "returns the rows" do
56
+ allow(RowTransformer).to receive(:run_on).with(:a_row).and_return(:a_tranformed_row)
57
+ allow(RowTransformer).to receive(:run_on).with(:another_row).and_return(:another_transformed_row)
58
+
59
+ expect(transformer.transformed_rows).to eq(%i[a_tranformed_row another_transformed_row])
60
+ end
61
+
62
+ context "when a row transformation error occurs" do
63
+ before do
64
+ allow(RowTransformer).to receive(:run_on).with(:a_row).and_return(:a_tranformed_row)
65
+
66
+ allow(RowTransformer)
67
+ .to receive(:run_on).with(:another_row)
68
+ .and_raise(TransformError, "transformation error")
69
+ end
70
+
71
+ it "raises an error message including the file name and line number of the row that was processed" do
72
+ expect { transformer.transformed_rows }.to raise_error(TransformError, <<~EOEXPECTEDERRORMSG)
73
+ source.csv:3: error while transforming row:
74
+ transformation error:
75
+ :another_row
76
+ EOEXPECTEDERRORMSG
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ticket/replicator/jira_project"
4
+
5
+ module Ticket
6
+ class Replicator
7
+ RSpec.describe JiraProject do
8
+ let(:project) { described_class.new(jira_auto_tool) }
9
+ let(:jira_auto_tool) { instance_double(Jira::Auto::Tool, jira_client: jira_client) }
10
+ let(:jira_client) { instance_double(JIRA::Client) }
11
+ let(:jira_client_project) { instance_double(JIRA::Resource::Project) }
12
+ let(:replicated_tickets) { 4.times.collect { instance_double(JIRA::Resource::Issue) } }
13
+
14
+ describe "#delete_all_tickets_from_the_expected_type" do
15
+ it do
16
+ allow(project).to receive(:replicated_tickets).and_return(replicated_tickets)
17
+ expect(replicated_tickets).to all(receive(:delete))
18
+
19
+ project.delete_all_tickets_from_the_expected_type
20
+ end
21
+ end
22
+
23
+ describe "#project_key" do
24
+ it do
25
+ expect(ENV).to receive(:fetch).with("TICKET_REPLICATOR_JIRA_PROJECT_KEY").and_return("PROJKEY")
26
+
27
+ expect(project.project_key).to eq("PROJKEY")
28
+ end
29
+ end
30
+
31
+ describe "#ticket_type_name" do
32
+ it do
33
+ expect(ENV).to receive(:fetch).with("TICKET_REPLICATOR_JIRA_TICKET_TYPE_NAME").and_return("Ticket Type Name")
34
+
35
+ expect(project.ticket_type_name).to eq("Ticket Type Name")
36
+ end
37
+ end
38
+
39
+ describe "#all_tickets" do
40
+ before do
41
+ expect(project).to receive(:all_jira_tickets).with(expected_ticket_jql).and_return(%i[a_ticket
42
+ another_ticket])
43
+ end
44
+
45
+ context "when not specifying any query" do
46
+ let(:expected_ticket_jql) { "project = PROJECT_KEY" }
47
+
48
+ it "returns all project tickets" do
49
+ expect(project).to receive(:project_key).and_return("PROJECT_KEY")
50
+
51
+ expect(project.all_tickets).to all be_a_kind_of(Ticket)
52
+ end
53
+ end
54
+
55
+ context "when providing a specific query" do
56
+ let(:expected_ticket_jql) { " a specific JQL query" }
57
+
58
+ it "uses the specified query" do
59
+ expect(project.all_tickets(expected_ticket_jql)).to all be_a_kind_of(Ticket)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#replicated_tickets" do
65
+ let(:expected_replicated_ticket_jql) do
66
+ <<-EOJQL
67
+ project = PROJKEY
68
+ AND issuetype = "Bug"
69
+ EOJQL
70
+ end
71
+
72
+ def build_non_replicated_ticket(summary)
73
+ double(Ticket, summary: summary, inspect: summary, replicated?: false)
74
+ end
75
+
76
+ def build_expected_ticket(source_id, summary)
77
+ double(Ticket, source_id: source_id, summary: summary, inspect: summary, replicated?: true)
78
+ end
79
+
80
+ let(:actual_replicated_tickets) do
81
+ [
82
+ build_expected_ticket("1", "SMAN-1 | a summary"),
83
+ build_expected_ticket("5", "SMAN-5 | a summary")
84
+ ]
85
+ end
86
+ let(:actual_tickets) do
87
+ [
88
+ build_non_replicated_ticket("a non replicated ticket"),
89
+ build_non_replicated_ticket("another non replicated ticket")
90
+ ] + actual_replicated_tickets
91
+ end
92
+
93
+ let(:expected_replicated_tickets) { %w[1 5].zip(actual_replicated_tickets).to_h }
94
+
95
+ it "fetches ticket pages" do
96
+ expect(project).to receive(:ticket_type_name).and_return("Bug")
97
+ expect(project).to receive(:project_key).and_return("PROJKEY")
98
+ expect(project).to receive(:all_tickets).with(expected_replicated_ticket_jql).and_return(actual_tickets)
99
+
100
+ expect(project.replicated_tickets).to eq(expected_replicated_tickets)
101
+ end
102
+ end
103
+
104
+ describe "#all_jira_tickets" do
105
+ let(:issue_accessor) { double("jira_client.Issue") }
106
+
107
+ let(:a_jql_query) { "a jql query" }
108
+
109
+ it "fetches ticket pages" do
110
+ expect(jira_client).to receive(:Issue).thrice.and_return(issue_accessor)
111
+
112
+ expect(issue_accessor).to receive(:jql).with(a_jql_query, { :max_results => 50, :start_at => 0 })
113
+ .and_return(%i[first_ticket second_ticket third_ticket])
114
+
115
+ expect(issue_accessor).to receive(:jql).with(a_jql_query, { :max_results => 50, :start_at => 50 })
116
+ .and_return(%i[forth_ticket fifth_ticket])
117
+
118
+ expect(issue_accessor).to receive(:jql).with(a_jql_query, { :max_results => 50, :start_at => 100 })
119
+ .and_return([])
120
+
121
+ expect(project.send(:all_jira_tickets, a_jql_query))
122
+ .to eq(%i[first_ticket second_ticket third_ticket forth_ticket fifth_ticket])
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ticket/replicator/replicated_summary"
4
+
5
+ RSpec.describe Ticket::Replicator::ReplicatedSummary do
6
+ describe ".build" do
7
+ let(:build_summary) { described_class.build("8192", "original summary") }
8
+
9
+ before do
10
+ expect(described_class).to receive(:new).with("8192", "original summary").and_call_original
11
+ end
12
+
13
+ it { expect(build_summary).to be_an_instance_of(described_class) }
14
+ end
15
+
16
+ describe ".match?" do
17
+ it { expect(described_class.match?("non replicated summary")).to be_falsy }
18
+ it { expect(described_class.match?("SMAN-1 | a replicated ticket summary")).to be_truthy }
19
+ end
20
+
21
+ describe ".parse" do
22
+ context "when the summary is not a valid replicated summary" do
23
+ it "raises error for a summary without a separator" do
24
+ expect { described_class.parse("SMAN-1234summary") }
25
+ .to raise_error(Ticket::Replicator::ReplicatedSummary::ParseError,
26
+ %("SMAN-1234summary": missing expected prefix format: ) \
27
+ "not matching #{Ticket::Replicator::ReplicatedSummary::TICKET_ID_PREFIX_REGEX.inspect}.")
28
+ end
29
+
30
+ it "raises error for summary with incorrect ticket prefix" do
31
+ expect { described_class.parse("TEST-1234 | summary") }
32
+ .to raise_error(Ticket::Replicator::ReplicatedSummary::ParseError)
33
+ end
34
+
35
+ it "raises error for empty summary" do
36
+ expect { described_class.parse("") }
37
+ .to raise_error(Ticket::Replicator::ReplicatedSummary::ParseError)
38
+ end
39
+ end
40
+
41
+ context "when the summary is a valid replicated summary" do
42
+ let(:parsed_summary) { described_class.parse("SMAN-1045 | another summary") }
43
+
44
+ before do
45
+ expect(described_class).to receive(:new).with("1045", "another summary").and_call_original
46
+ end
47
+
48
+ it { expect(parsed_summary).to be_an_instance_of(described_class) }
49
+ end
50
+ end
51
+
52
+ describe ".jql_pattern" do
53
+ it { expect(described_class.jql_pattern).to eq("SMAN-% | ") }
54
+ end
55
+
56
+ describe "fields" do
57
+ let(:replicated_summary) { described_class.send(:new, "9005", "some summary") }
58
+
59
+ describe "#source_summary" do
60
+ it { expect(replicated_summary.source_summary).to eq("some summary") }
61
+ end
62
+
63
+ describe "#source_id" do
64
+ it { expect(replicated_summary.source_id).to eq("9005") }
65
+ end
66
+ describe "#to_s" do
67
+ it { expect(replicated_summary.to_s).to eq("SMAN-9005 | some summary") }
68
+ end
69
+ end
70
+ end