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,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+ require "rspec"
5
+ require "ticket/replicator/ticket"
6
+
7
+ module Ticket
8
+ # rubocop:disable Metrics/ClassLength
9
+ class Replicator
10
+ RSpec.describe Ticket do
11
+ let(:jira_ticket_properties_string) do
12
+ <<-EOJTPS
13
+ {
14
+ "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
15
+ "id": "13661",#{" "}
16
+ "self": "https://cbroult.atlassian.net/rest/api/2/issue/13661",
17
+ "key": "TRCIDEVLX-1",
18
+ "fields": {
19
+ "statuscategorychangedate": "2025-04-02T19:39:22.438+0200",
20
+ "issuetype": {
21
+ "self": "https://cbroult.atlassian.net/rest/api/2/issuetype/10022",
22
+ "id": "10022",
23
+ "description": "Bugs track problems or errors.",
24
+ "iconUrl": "https://cbroult.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10303?size=medium",
25
+ "name": "Bug",
26
+ "subtask": false,
27
+ "avatarId": 10303,
28
+ "entityId": "a23acedd-54b7-4e0c-846a-12588361ba53",
29
+ "hierarchyLevel": 0
30
+ },
31
+ "components": [],
32
+ "timeoriginalestimate": null,
33
+ "description": null,
34
+ "project": {
35
+ "self": "https://cbroult.atlassian.net/rest/api/2/project/10009",
36
+ "id": "10009",#{" "}
37
+ "key": "TRCIDEVLX",
38
+ "name": "TR CI/CD - Dev - Linux",
39
+ "projectTypeKey": "software",
40
+ "simplified": true,
41
+ "avatarUrls": {
42
+ "48x48": "https://cbroult.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10413",
43
+ "24x24": "https://cbroult.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10413?size=small",
44
+ "16x16": "https://cbroult.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10413?size=xsmall",#{" "}
45
+ "32x32": "https://cbroult.atlassian.net/rest/api/2/universal_avatar/view/type/project/avatar/10413?size=medium"
46
+ }
47
+ },
48
+ "fixVersions": [],
49
+ "statusCategory": {
50
+ "self": "https://cbroult.atlassian.net/rest/api/2/statuscategory/2",
51
+ "id": 2,
52
+ "key": "new",
53
+ "colorName": "blue-gray",#{" "}
54
+ "name": "To Do"
55
+ },
56
+ "resolution": null,
57
+ "security": null,
58
+ "resolutiondate": null,
59
+ "summary": "SMAN-16384 | Login page randomly fails to load CSS assets (10001)",
60
+ "lastViewed": null,
61
+ "watches": {
62
+ "self": "https://cbroult.atlassian.net/rest/api/2/issue/TRCIDEVLX-1/watchers",
63
+ "watchCount": 1,
64
+ "isWatching": true
65
+ },
66
+ "created": "2025-04-02T19:39:21.673+0200",
67
+ "customfield_10020": null,
68
+ "customfield_10021": null,
69
+ "reporter": {
70
+ "self": "https://cbroult.atlassian.net/rest/api/2/user?accountId=712020%3Acbaffa92-1b2a-41b7-be0b-9199dcdf927c",
71
+ "accountId": "712020:cbaffa92-1b2a-41b7-be0b-9199dcdf927c",
72
+ "emailAddress": "cbroult@yahoo.com",
73
+ "avatarUrls": {
74
+ "48x48": "https://secure.gravatar.com/avatar/adb3120007b0819ce4e6e5535dabbfd9?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FCB-0.png",
75
+ "24x24": "https://secure.gravatar.com/avatar/adb3120007b0819ce4e6e5535dabbfd9?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FCB-0.png",
76
+ "16x16": "https://secure.gravatar.com/avatar/adb3120007b0819ce4e6e5535dabbfd9?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FCB-0.png",
77
+ "32x32": "https://secure.gravatar.com/avatar/adb3120007b0819ce4e6e5535dabbfd9?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FCB-0.png"
78
+ },
79
+ "displayName": "Christophe Broult",
80
+ "active": true,
81
+ "timeZone": "Europe/Berlin",
82
+ "accountType": "atlassian"
83
+ },
84
+ "priority": {
85
+ "self": "https://cbroult.atlassian.net/rest/api/2/priority/3",
86
+ "iconUrl": "https://cbroult.atlassian.net/images/icons/priorities/medium.svg",
87
+ "name": "Medium",
88
+ "id": "3"
89
+ },
90
+ "customfield_10001": null,
91
+ "labels": [],
92
+ "customfield_10016": null,
93
+ "customfield_10116": null,
94
+ "environment": null,
95
+ "customfield_10019": "0|i000ev:",
96
+ "versions": [],
97
+ "duedate": null,
98
+ "votes": {
99
+ "self": "https://cbroult.atlassian.net/rest/api/2/issue/TRCIDEVLX-1/votes",
100
+ "votes": 0,
101
+ "hasVoted": false
102
+ },
103
+ "issuelinks": [],
104
+ "assignee": null,
105
+ "updated": "2025-04-02T19:39:21.905+0200",
106
+ "status": {
107
+ "self": "https://cbroult.atlassian.net/rest/api/2/status/10023",
108
+ "description": "",
109
+ "iconUrl": "https://cbroult.atlassian.net/",
110
+ "name": "To Do",
111
+ "id": "10023",
112
+ "statusCategory": {
113
+ "self": "https://cbroult.atlassian.net/rest/api/2/statuscategory/2",
114
+ "id": 2,
115
+ "key": "new",
116
+ "colorName": "blue-gray",
117
+ "name": "To Do"
118
+ }
119
+ }
120
+ }
121
+ }
122
+ EOJTPS
123
+ end
124
+
125
+ let(:jira_ticket_properties_hash) { Psych.load(jira_ticket_properties_string) }
126
+ let(:field_navigator) { double("field_navigator") }
127
+
128
+ before(:each) do
129
+ %i[summary status resolution priority].each do |field|
130
+ allow(field_navigator).to receive(:name_to_id).with(field).and_return(true)
131
+ end
132
+
133
+ allow(field_navigator).to receive(:name_to_id).and_return(true)
134
+ end
135
+
136
+ let(:jira_auto_tool) { instance_double(Jira::Auto::Tool) }
137
+ let(:jira_client) { instance_double(JIRA::Client, Field: field_navigator) }
138
+
139
+ # let(:jira_ticket) { OpenStruct.new(jira_ticket_properties_hash["fields"]) }
140
+ let(:jira_ticket) { JIRA::Resource::Issue.build(jira_client, jira_ticket_properties_hash) }
141
+
142
+ let(:ticket) { described_class.new(jira_auto_tool, jira_ticket) }
143
+
144
+ describe "#source_id" do
145
+ it { expect(ticket.source_id).to eq("16384") }
146
+ end
147
+
148
+ describe "#summary" do
149
+ it { expect(ticket.summary).to eq("SMAN-16384 | Login page randomly fails to load CSS assets (10001)") }
150
+ end
151
+
152
+ describe "replicated?" do
153
+ let(:ticket) { described_class.new(jira_auto_tool, jira_ticket) }
154
+ let(:jira_ticket) { double(JIRA::Resource::Issue, summary: actual_summary) }
155
+
156
+ context "when replicated" do
157
+ let(:actual_summary) { "SMAN-1 | summary of a replicated ticket" }
158
+ it { expect(ticket.replicated?).to be_truthy }
159
+ end
160
+
161
+ context "when non replicated" do
162
+ let(:actual_summary) { "summary of a non replicated ticket" }
163
+ it { expect(ticket.replicated?).to be_falsy }
164
+ end
165
+ end
166
+
167
+ describe "#status" do
168
+ it { expect(ticket.status).to eq("To Do") }
169
+ end
170
+
171
+ describe "#resolution" do
172
+ it { expect(ticket.resolution).to eq("") }
173
+ end
174
+
175
+ describe "#priority" do
176
+ it { expect(ticket.priority).to eq("Medium") }
177
+ end
178
+
179
+ describe "<=>" do
180
+ let(:ticket) { build_ticket "SMAN-16384 | Test ticket" }
181
+
182
+ def build_ticket(summary)
183
+ attributes = Marshal.load(Marshal.dump(jira_ticket_properties_hash))
184
+ attributes["fields"]["summary"] = summary
185
+ described_class.new(jira_auto_tool, JIRA::Resource::Issue.build(jira_client, attributes))
186
+ end
187
+
188
+ it "returns 0 for identical tickets" do
189
+ expect(ticket <=> ticket).to eq(0) # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
190
+ end
191
+
192
+ it "returns 0 for tickets with identical source_id" do
193
+ other_ticket = build_ticket(ticket.summary)
194
+
195
+ expect(ticket <=> other_ticket).to eq(0)
196
+ end
197
+
198
+ it "returns -1 when comparing ticket with higher source_id" do
199
+ other_ticket = build_ticket("SMAN-16385 | Test ticket")
200
+ expect(ticket <=> other_ticket).to eq(-1)
201
+ end
202
+
203
+ it "returns 1 when comparing ticket with lower source_id" do
204
+ other_ticket = build_ticket("SMAN-16383 | Test ticket")
205
+ expect(ticket <=> other_ticket).to eq(1)
206
+ end
207
+
208
+ it "returns nil when comparing with nil" do
209
+ expect(ticket <=> nil).to be_nil
210
+ end
211
+
212
+ it "returns nil when comparing ticket with nil source_id" do
213
+ other_ticket = build_ticket("Summary with no source id prefix")
214
+ expect { ticket <=> other_ticket }.to raise_error(ReplicatedSummary::ParseError)
215
+ end
216
+ end
217
+
218
+ describe "#delete" do
219
+ it do
220
+ expect(jira_ticket).to receive(:delete)
221
+
222
+ ticket.delete
223
+ end
224
+
225
+ it "raises an exception when deletion fails" do
226
+ expect(jira_ticket).to receive(:delete).and_raise(JIRA::HTTPError.new("Not found"))
227
+
228
+ expect { ticket.delete }.to raise_error(JIRA::HTTPError)
229
+ end
230
+ end
231
+
232
+ describe "#transition_to" do
233
+ let(:transitioner) { instance_double(TicketStatusTransitioner) }
234
+ it do
235
+ expect(TicketStatusTransitioner).to receive(:new).with(ticket).and_return(transitioner)
236
+ expect(transitioner).to receive(:transition_to).with("Testing")
237
+
238
+ ticket.transition_to("Testing")
239
+ end
240
+ end
241
+ end
242
+ end
243
+ # rubocop:enable Metrics/ClassLength
244
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "ticket/replicator/ticket_status_transitioner"
5
+
6
+ module Ticket
7
+ class Replicator
8
+ RSpec.describe TicketStatusTransitioner do
9
+ let(:transitioner) { described_class.send(:new, ticket) }
10
+
11
+ let(:ticket) do
12
+ double(Ticket, key: "PROJKEY-16", status: "Todo", jira_auto_tool: jira_auto_tool, jira_client: jira_client)
13
+ end
14
+
15
+ let(:jira_auto_tool) { instance_double(Jira::Auto::Tool, jira_client: jira_client) }
16
+ let(:jira_client) { instance_double(JIRA::Client) }
17
+
18
+ describe "#transistion_to" do
19
+ let(:expected_payload) { { transition: { id: 131 } }.to_json }
20
+ let(:expected_headers) { { "Content-Type" => "application/json" } }
21
+
22
+ let(:http_response) do
23
+ instance_double(Net::HTTPResponse, code: 200, body: [].to_json)
24
+ end
25
+
26
+ it "transition the ticket status as per the ticket workflow" do
27
+ expect(jira_auto_tool).to receive(:jira_url)
28
+ .with("/rest/api/2/issue/PROJKEY-16/transitions")
29
+ .and_return("/transition/ticket/path")
30
+
31
+ expect(transitioner)
32
+ .to receive(:transition_payload_for).with("Pending Deployment").and_return(expected_payload)
33
+
34
+ expect(jira_client).to receive(:post)
35
+ .with("/transition/ticket/path", expected_payload, expected_headers)
36
+ .and_return(http_response)
37
+
38
+ transitioner.transition_to("Pending Deployment")
39
+ end
40
+
41
+ context "when the ticket is already in the expected status" do
42
+ it "does not transition the ticket" do
43
+ expect(ticket).to receive_messages(status: "a status")
44
+ expect(jira_client).not_to receive(:post).with(:anything)
45
+
46
+ transitioner.transition_to("a status")
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#transition_payload_for" do
52
+ let(:available_transitions) do
53
+ [
54
+ { "id" => "701",
55
+ "name" => "Close Issue",
56
+ "to" =>
57
+ { "name" => "Closed",
58
+ "id" => "6" } },
59
+ { "id" => "3",
60
+ "name" => "Reopen Issue",
61
+ "to" =>
62
+ { "name" => "Reopened",
63
+ "id" => "4" } }
64
+ ]
65
+ end
66
+
67
+ it do
68
+ expect(transitioner).to receive(:available_transitions).and_return(available_transitions)
69
+
70
+ expect(transitioner.transition_payload_for("Closed")).to eq({ transition: { id: "701" } }.to_json)
71
+ end
72
+ end
73
+
74
+ describe "#available_transitions" do
75
+ let(:actual_ticket_with_transition_information_response) do
76
+ { "key" => "PROJKEY-16",
77
+ "summary" => "SMAN-90056 | a summary",
78
+ "transitions" =>
79
+ [
80
+ { "id" => "701",
81
+ "name" => "Close Issue",
82
+ "to" =>
83
+ { "name" => "Closed",
84
+ "id" => "6" } },
85
+ { "id" => "3",
86
+ "name" => "Reopen Issue",
87
+ "to" =>
88
+ { "name" => "Reopened",
89
+ "id" => "4" } }
90
+ ] }
91
+ end
92
+
93
+ let(:http_response) do
94
+ instance_double(Net::HTTPResponse, body: actual_ticket_with_transition_information_response.to_json)
95
+ end
96
+
97
+ it "returns the available transitions for the ticket" do
98
+ expect(jira_auto_tool)
99
+ .to receive(:jira_url).with("/rest/api/2/issue/PROJKEY-16?expand=transitions")
100
+ .and_return("/path/to/fetch/issue_with_transitions")
101
+
102
+ expect(jira_client).to receive(:get).with("/path/to/fetch/issue_with_transitions")
103
+ .and_return(http_response)
104
+
105
+ expect(transitioner.available_transitions).to eq(
106
+ [
107
+ { "id" => "701",
108
+ "name" => "Close Issue",
109
+ "to" =>
110
+ { "name" => "Closed",
111
+ "id" => "6" } },
112
+ { "id" => "3",
113
+ "name" => "Reopen Issue",
114
+ "to" =>
115
+ { "name" => "Reopened",
116
+ "id" => "4" } }
117
+ ]
118
+ )
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ticket/replicator"
4
+
5
+ module Ticket
6
+ class Replicator
7
+ RSpec.describe Replicator do
8
+ it("has a version number") { expect(VERSION).not_to be nil }
9
+
10
+ let(:replicator) { Replicator.new }
11
+
12
+ it { expect(replicator.jira_project).to be_a(JiraProject) }
13
+ it { expect(replicator.jira_client).to be_a(JIRA::Client) }
14
+ it { expect(replicator.jira_auto_tool).to be_a(Jira::Auto::Tool) }
15
+
16
+ describe "#load" do
17
+ let(:transformed_files) { %w[transformed_file1 transformed_file2] }
18
+
19
+ before do
20
+ allow(replicator).to receive_messages(transformed_files: transformed_files)
21
+ allow(replicator).to receive(:jira_project).and_return(:jira_project)
22
+ end
23
+
24
+ it "transforms extracted files in the queue" do
25
+ transformed_files.each do |transformed_file|
26
+ expect(FileLoader).to receive(:run_on).with(:jira_project, transformed_file)
27
+ end
28
+
29
+ replicator.load
30
+ end
31
+ end
32
+
33
+ describe "#ticket_queue_transform_and_load" do
34
+ let(:extracted_files) { %w[extracted_file1 extracted_file2] }
35
+
36
+ before { allow(replicator).to receive_messages(extracted_files: extracted_files) }
37
+
38
+ it do
39
+ extracted_files.each do |extracted_file|
40
+ expect(FileReplicator).to receive(:run_on).with(replicator, extracted_file)
41
+ end
42
+
43
+ replicator.ticket_queue_transform_and_load
44
+ end
45
+ end
46
+
47
+ describe "#transformed_queue_clear" do
48
+ it "clears the transformed queue" do
49
+ expect(replicator).to receive_messages(transformed_folder: "path/to/transformed_folder")
50
+ expect(FileUtils).to receive(:rm_rf).with("path/to/transformed_folder")
51
+
52
+ replicator.transformed_queue_clear
53
+ end
54
+ end
55
+
56
+ describe "#transform" do
57
+ let(:extracted_files) { %w[extracted_file1 extracted_file2] }
58
+
59
+ before { allow(replicator).to receive_messages(extracted_files: extracted_files) }
60
+
61
+ it("transforms extracted files in the queue") do
62
+ extracted_files.each do |extracted_file|
63
+ expect(FileReplicator).to receive(:transform).with(replicator, extracted_file)
64
+ end
65
+
66
+ replicator.transform
67
+ end
68
+ end
69
+
70
+ describe "#extracted_files" do
71
+ it "returns a list of files" do
72
+ allow(replicator).to receive(:queue_files).with("queue/10.extracted").and_return(%w[file1 file2])
73
+
74
+ expect(replicator.extracted_files).to eq(%w[file1 file2])
75
+ end
76
+ end
77
+
78
+ describe "#transformed_files" do
79
+ it "returns a list of files" do
80
+ allow(replicator).to receive(:queue_files).with("queue/20.transformed").and_return(%w[file1 file2])
81
+
82
+ expect(replicator.transformed_files).to eq(%w[file1 file2])
83
+ end
84
+ end
85
+
86
+ describe "#queue_files" do
87
+ it "returns a list of files in the specified queue folder" do
88
+ expect(Dir).to receive(:glob).with("path/to/queue/folder/**/*").and_return(%w[file1 file2])
89
+
90
+ expect(replicator.send(:queue_files, "path/to/queue/folder")).to eq(%w[file1 file2])
91
+ end
92
+ end
93
+
94
+ shared_examples "a queue sub folder" do |sub_folder_method, expected_folder_path|
95
+ before { allow(replicator).to receive(:queue_dir).and_return("path/to/queue") }
96
+
97
+ it("is a queue sub folder") { expect(replicator.send(sub_folder_method)).to eq(expected_folder_path) }
98
+
99
+ it "automatically creates the queue sub folder if it does not exist" do
100
+ expect(FileUtils).to receive(:mkdir_p).with(expected_folder_path)
101
+
102
+ replicator.send(sub_folder_method)
103
+ end
104
+ end
105
+
106
+ it_behaves_like "a queue sub folder", :extracted_folder, "path/to/queue/10.extracted"
107
+ it_behaves_like "a queue sub folder", :transformed_folder, "path/to/queue/20.transformed"
108
+ it_behaves_like "a queue sub folder", :archived_folder, "path/to/queue/30.archived"
109
+
110
+ describe "#queue_folder" do
111
+ it("returns the queue folder") { expect(replicator.queue_folder).to eq("queue") }
112
+
113
+ it "automatically creates the queue folder if it does not exist" do
114
+ expect(FileUtils).to receive(:mkdir_p).with("queue")
115
+
116
+ replicator.queue_folder
117
+ end
118
+
119
+ context "when a subfolder is given" do
120
+ it "returns the queue folder" do
121
+ expect(FileUtils).to receive(:mkdir_p).with("queue/sub_folder")
122
+
123
+ expect(replicator.queue_folder("sub_folder")).to eq("queue/sub_folder")
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "#home_dir" do
129
+ it { expect(replicator.home_dir).to eq(File.expand_path("../../", __dir__)) }
130
+ end
131
+
132
+ describe "#setup" do
133
+ it { expect(replicator.setup).to be_a(Setup) }
134
+ end
135
+ end
136
+ end
137
+ end
data/transformed_file1 ADDED
@@ -0,0 +1 @@
1
+ "ID","Status","Resolution","Priority","Team","Summary"