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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Guardfile +158 -0
- data/LICENSE.txt +21 -0
- data/README.md +136 -0
- data/Rakefile +23 -0
- data/bin/ticket-replicator +67 -0
- data/config/examples/ticket-replicator.mappings.yml +54 -0
- data/cucumber.yml +7 -0
- data/features/extract-sap-solution-manager-defect-tickets.feature +45 -0
- data/features/load_tickets_in_jira.feature +129 -0
- data/features/setup_ticket_replicator.feature +85 -0
- data/features/step_definitions/anonymized_sample.xlsx +0 -0
- data/features/step_definitions/anonymized_sample.xlsx:Zone.Identifier +3 -0
- data/features/step_definitions/execution_context_steps.rb +13 -0
- data/features/step_definitions/extract_defect_tickets_from_sap_solution_manager_steps.rb.rb +29 -0
- data/features/step_definitions/load_tickets_in_jira_steps.rb +47 -0
- data/features/step_definitions/transform_solution_manager_tickets_steps.rb +21 -0
- data/features/support/10.setup_cucumber.rb +10 -0
- data/features/support/env.rb +15 -0
- data/features/support/hooks.rb +13 -0
- data/features/support/manage_mock_sap_solution_manager.rb.DISABLED +12 -0
- data/features/support/mocks/mock_defect_ticket_server.rb.DISABLED +251 -0
- data/features/support/setup_rspec.rb +15 -0
- data/features/support/setup_simplecov.rb +5 -0
- data/features/transform-solution-manager-tickets-into-jira-loadable-tickets.feature +313 -0
- data/features/transform_and_load_extracted_ticket_queue.feature +121 -0
- data/lib/tasks/version.rake +55 -0
- data/lib/ticket/replicator/defect_export_automation.rb.DISABLED +128 -0
- data/lib/ticket/replicator/file_loader.rb +46 -0
- data/lib/ticket/replicator/file_replicator.rb +67 -0
- data/lib/ticket/replicator/file_transformer/for_csv.rb +22 -0
- data/lib/ticket/replicator/file_transformer/for_xlsx.rb +34 -0
- data/lib/ticket/replicator/file_transformer.rb +70 -0
- data/lib/ticket/replicator/jira_project.rb +65 -0
- data/lib/ticket/replicator/replicated_summary.rb +73 -0
- data/lib/ticket/replicator/row_loader.rb +109 -0
- data/lib/ticket/replicator/row_transformer.rb +126 -0
- data/lib/ticket/replicator/s_a_p_solution_manager_client.rb.DISABLED +169 -0
- data/lib/ticket/replicator/setup.rb +49 -0
- data/lib/ticket/replicator/ticket.rb +70 -0
- data/lib/ticket/replicator/ticket_status_transitioner.rb +45 -0
- data/lib/ticket/replicator/version.rb +7 -0
- data/lib/ticket/replicator.rb +90 -0
- data/sig/ticket/replicator.rbs +6 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/ticket/replicator/file_loader_spec.rb +77 -0
- data/spec/ticket/replicator/file_replicator_spec.rb +153 -0
- data/spec/ticket/replicator/file_transformer/for_csv_spec.rb +52 -0
- data/spec/ticket/replicator/file_transformer/for_xlsx_spec.rb +52 -0
- data/spec/ticket/replicator/file_transformer_spec.rb +83 -0
- data/spec/ticket/replicator/jira_project_spec.rb +127 -0
- data/spec/ticket/replicator/replicated_summary_spec.rb +70 -0
- data/spec/ticket/replicator/row_loader_spec.rb +245 -0
- data/spec/ticket/replicator/row_transformer_spec.rb +234 -0
- data/spec/ticket/replicator/setup_spec.rb +80 -0
- data/spec/ticket/replicator/ticket_spec.rb +244 -0
- data/spec/ticket/replicator/ticket_status_transitioner_spec.rb +123 -0
- data/spec/ticket/replicator_spec.rb +137 -0
- data/transformed_file1 +1 -0
- 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
|