jira-auto-tool 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 (139) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +291 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/Guardfile +105 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +159 -0
  9. data/Rakefile +20 -0
  10. data/bin/jira-auto-tool +57 -0
  11. data/bin/jira-auto-tool.bat +2 -0
  12. data/bin/setup +8 -0
  13. data/bin/setup-dev-win.bat +3 -0
  14. data/cucumber.yml +7 -0
  15. data/features/align_sprint_time_in_dates.feature +33 -0
  16. data/features/assign_tickets_to_team_sprints.feature +73 -0
  17. data/features/cache_boards.feature +24 -0
  18. data/features/control_http_request_rate_limit.feature +17 -0
  19. data/features/create_sprints_using_existing_ones_as_reference.feature +79 -0
  20. data/features/list_boards.feature +12 -0
  21. data/features/list_project_fields.feature +89 -0
  22. data/features/list_sprint_prefixes.feature +77 -0
  23. data/features/quarterly_add_sprints_using_existing_ones_as_a_reference.feature +71 -0
  24. data/features/quarterly_create_sprints_until_specific_date.feature +75 -0
  25. data/features/quarterly_rename_sprints.feature +179 -0
  26. data/features/rename_sprints.feature +203 -0
  27. data/features/self_documented_command_line.feature +15 -0
  28. data/features/sprint_filtering.feature +111 -0
  29. data/features/step_definitions/execution_context_steps.rb +33 -0
  30. data/features/step_definitions/jira_board_steps.rb +102 -0
  31. data/features/step_definitions/jira_ticket_steps.rb +63 -0
  32. data/features/support/10.setup_cucumber.rb +10 -0
  33. data/features/support/env.rb +25 -0
  34. data/features/support/hooks.rb +25 -0
  35. data/features/support/setup_rspec.rb +14 -0
  36. data/features/support/setup_simplecov.rb +5 -0
  37. data/features/update_sprint_end_date_and_shift_following_ones.feature +52 -0
  38. data/lib/jira/auto/tool/board/cache.rb +67 -0
  39. data/lib/jira/auto/tool/board/unavailable_board.rb +36 -0
  40. data/lib/jira/auto/tool/board.rb +105 -0
  41. data/lib/jira/auto/tool/board_controller/options.rb +32 -0
  42. data/lib/jira/auto/tool/board_controller.rb +88 -0
  43. data/lib/jira/auto/tool/common_options.rb +37 -0
  44. data/lib/jira/auto/tool/config/options.rb +19 -0
  45. data/lib/jira/auto/tool/config.rb +64 -0
  46. data/lib/jira/auto/tool/fetch_custom_field_options.rb +47 -0
  47. data/lib/jira/auto/tool/field.rb +59 -0
  48. data/lib/jira/auto/tool/field_controller.rb +50 -0
  49. data/lib/jira/auto/tool/field_option.rb +35 -0
  50. data/lib/jira/auto/tool/get_createmeta_for_project.rb +24 -0
  51. data/lib/jira/auto/tool/helpers/environment_based_value.rb +96 -0
  52. data/lib/jira/auto/tool/helpers/option_parser.rb +16 -0
  53. data/lib/jira/auto/tool/helpers/overridable_time.rb +18 -0
  54. data/lib/jira/auto/tool/helpers/pagination.rb +50 -0
  55. data/lib/jira/auto/tool/jira_http_options.rb +20 -0
  56. data/lib/jira/auto/tool/next_sprint_creator.rb +60 -0
  57. data/lib/jira/auto/tool/performer/options.rb +76 -0
  58. data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +42 -0
  59. data/lib/jira/auto/tool/performer/prefix_sprint_updater.rb +42 -0
  60. data/lib/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator.rb +60 -0
  61. data/lib/jira/auto/tool/performer/quarterly_sprint_renamer.rb +19 -0
  62. data/lib/jira/auto/tool/performer/sprint_end_date_updater.rb +55 -0
  63. data/lib/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator.rb +19 -0
  64. data/lib/jira/auto/tool/performer/sprint_renamer/next_name_generator.rb +47 -0
  65. data/lib/jira/auto/tool/performer/sprint_renamer.rb +55 -0
  66. data/lib/jira/auto/tool/performer/sprint_time_in_dates_aligner.rb +52 -0
  67. data/lib/jira/auto/tool/project/options.rb +22 -0
  68. data/lib/jira/auto/tool/project/ticket_fields.rb +70 -0
  69. data/lib/jira/auto/tool/project.rb +40 -0
  70. data/lib/jira/auto/tool/rate_limited_jira_client.rb +50 -0
  71. data/lib/jira/auto/tool/request_builder/field_context_fetcher.rb +78 -0
  72. data/lib/jira/auto/tool/request_builder/field_option_fetcher.rb +54 -0
  73. data/lib/jira/auto/tool/request_builder/get.rb +29 -0
  74. data/lib/jira/auto/tool/request_builder/sprint_creator.rb +112 -0
  75. data/lib/jira/auto/tool/request_builder/sprint_state_updater.rb +60 -0
  76. data/lib/jira/auto/tool/request_builder.rb +89 -0
  77. data/lib/jira/auto/tool/setup_logging.rb +35 -0
  78. data/lib/jira/auto/tool/sprint/name.rb +105 -0
  79. data/lib/jira/auto/tool/sprint/prefix.rb +66 -0
  80. data/lib/jira/auto/tool/sprint.rb +183 -0
  81. data/lib/jira/auto/tool/sprint_controller/options.rb +61 -0
  82. data/lib/jira/auto/tool/sprint_controller.rb +152 -0
  83. data/lib/jira/auto/tool/sprint_state_controller.rb +58 -0
  84. data/lib/jira/auto/tool/team.rb +23 -0
  85. data/lib/jira/auto/tool/team_sprint_prefix_mapper/options.rb +27 -0
  86. data/lib/jira/auto/tool/team_sprint_prefix_mapper.rb +62 -0
  87. data/lib/jira/auto/tool/team_sprint_ticket_dispatcher.rb +76 -0
  88. data/lib/jira/auto/tool/ticket.rb +110 -0
  89. data/lib/jira/auto/tool/until_date.rb +68 -0
  90. data/lib/jira/auto/tool/version.rb +9 -0
  91. data/lib/jira/auto/tool.rb +216 -0
  92. data/sig/jira/sprint/tool.rbs +8 -0
  93. data/spec/jira/auto/tool/board/cache_spec.rb +179 -0
  94. data/spec/jira/auto/tool/board/unavailable_board_spec.rb +34 -0
  95. data/spec/jira/auto/tool/board_controller/options_spec.rb +52 -0
  96. data/spec/jira/auto/tool/board_controller_spec.rb +154 -0
  97. data/spec/jira/auto/tool/board_spec.rb +163 -0
  98. data/spec/jira/auto/tool/common_options_spec.rb +49 -0
  99. data/spec/jira/auto/tool/config_spec.rb +108 -0
  100. data/spec/jira/auto/tool/field_controller_spec.rb +121 -0
  101. data/spec/jira/auto/tool/field_option_spec.rb +42 -0
  102. data/spec/jira/auto/tool/field_spec.rb +99 -0
  103. data/spec/jira/auto/tool/helpers/environment_based_value_spec.rb +21 -0
  104. data/spec/jira/auto/tool/helpers/option_parser_spec.rb +21 -0
  105. data/spec/jira/auto/tool/helpers/overridable_time_spec.rb +43 -0
  106. data/spec/jira/auto/tool/helpers/pagination_spec.rb +72 -0
  107. data/spec/jira/auto/tool/jira_http_options_spec.rb +32 -0
  108. data/spec/jira/auto/tool/next_sprint_creator_spec.rb +85 -0
  109. data/spec/jira/auto/tool/performer/option_spec.rb +55 -0
  110. data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +62 -0
  111. data/spec/jira/auto/tool/performer/prefix_sprint_updater_spec.rb +35 -0
  112. data/spec/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator_spec.rb +175 -0
  113. data/spec/jira/auto/tool/performer/quarterly_sprint_renamer_spec.rb +239 -0
  114. data/spec/jira/auto/tool/performer/sprint_end_date_updater_spec.rb +90 -0
  115. data/spec/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator_spec.rb +12 -0
  116. data/spec/jira/auto/tool/performer/sprint_renamer/next_name_generator_spec.rb +129 -0
  117. data/spec/jira/auto/tool/performer/sprint_renamer_spec.rb +240 -0
  118. data/spec/jira/auto/tool/performer/sprint_time_in_dates_aligner_spec.rb +132 -0
  119. data/spec/jira/auto/tool/project/ticket_fields_spec.rb +390 -0
  120. data/spec/jira/auto/tool/project_spec.rb +31 -0
  121. data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +82 -0
  122. data/spec/jira/auto/tool/request_builder/field_context_fetcher_spec.rb +54 -0
  123. data/spec/jira/auto/tool/request_builder/field_option_fetcher_spec.rb +64 -0
  124. data/spec/jira/auto/tool/request_builder/get_spec.rb +40 -0
  125. data/spec/jira/auto/tool/request_builder/sprint_creator_spec.rb +179 -0
  126. data/spec/jira/auto/tool/request_builder/sprint_state_updater_spec.rb +31 -0
  127. data/spec/jira/auto/tool/request_builder_spec.rb +73 -0
  128. data/spec/jira/auto/tool/sprint/name_spec.rb +101 -0
  129. data/spec/jira/auto/tool/sprint/prefix_spec.rb +207 -0
  130. data/spec/jira/auto/tool/sprint_controller_spec.rb +406 -0
  131. data/spec/jira/auto/tool/sprint_spec.rb +309 -0
  132. data/spec/jira/auto/tool/team_spec.rb +21 -0
  133. data/spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb +97 -0
  134. data/spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb +232 -0
  135. data/spec/jira/auto/tool/ticket_spec.rb +116 -0
  136. data/spec/jira/auto/tool/until_date_spec.rb +80 -0
  137. data/spec/jira/auto/tool_spec.rb +458 -0
  138. data/spec/spec_helper.rb +42 -0
  139. metadata +368 -0
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/request_builder/get"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class RequestBuilder
9
+ RSpec.describe Get do
10
+ let(:jira_client) { instance_double(JIRA::Client) }
11
+ let(:request_builder) { described_class.new(jira_client) }
12
+
13
+ describe "#request_payload" do
14
+ it "returns nil" do
15
+ expect(request_builder.request_payload).to be_nil
16
+ end
17
+ end
18
+
19
+ describe "#http_verb" do
20
+ it "returns :get" do
21
+ expect(request_builder.http_verb).to eq(:get)
22
+ end
23
+ end
24
+
25
+ describe "#expected_response" do
26
+ it "returns 200" do
27
+ expect(request_builder.expected_response).to eq(200)
28
+ end
29
+ end
30
+
31
+ describe "#request_headers" do
32
+ it "returns nil" do
33
+ expect(request_builder.request_headers).to be_nil
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/request_builder/sprint_state_updater"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ class RequestBuilder
10
+ RSpec.describe SprintCreator do
11
+ let(:sprint_creator_instance) { described_class.new(jira_client, 32, attributes) }
12
+ let(:attributes) { { name: "a_name", start_date: "2024-12-19 13:16 UTC", length_in_days: 14 } }
13
+
14
+ let(:jira_client) { instance_spy(JIRA::Client, options: { context_path: :a_context_path }).as_null_object }
15
+ let(:tool) { instance_double(Tool, jira_client: jira_client) }
16
+
17
+ it { expect(sprint_creator_instance.send(:request_path)).to eq("/rest/agile/1.0/sprint") }
18
+
19
+ it { expect(sprint_creator_instance.send(:http_verb)).to eq(:post) }
20
+
21
+ it { expect(sprint_creator_instance.send(:expected_response)).to eq(201) }
22
+
23
+ def get_date(date_string)
24
+ Time.parse(date_string)
25
+ end
26
+
27
+ describe "#start_date" do
28
+ let(:actual_start_date) { sprint_creator_instance.send(:start_date) }
29
+
30
+ context "when no start date is provided" do
31
+ let(:attributes) { {} }
32
+
33
+ it { expect(actual_start_date).to be_nil }
34
+ end
35
+
36
+ context "when a start date is an empty string" do
37
+ let(:attributes) { { start_date: "" } }
38
+
39
+ it { expect(actual_start_date).to be_nil }
40
+ end
41
+
42
+ context "when a start date is provided" do
43
+ let(:attributes) { { start_date: "2024-12-19 13:16 UTC" } }
44
+
45
+ it { expect(actual_start_date).to eq(get_date("2024-12-19 13:16 UTC")) }
46
+ end
47
+ end
48
+
49
+ describe "#end_date" do
50
+ let(:actual_end_date) { sprint_creator_instance.send(:end_date) }
51
+
52
+ context "when no end date is provided" do
53
+ let(:attributes) { {} }
54
+
55
+ it { expect(actual_end_date).to be_nil }
56
+ end
57
+
58
+ context "when end date is an empty string" do
59
+ let(:attributes) { { end_date: "" } }
60
+
61
+ it { expect(actual_end_date).to be_nil }
62
+ end
63
+
64
+ context "when no end date specified and start date with length in days are provided" do
65
+ let(:attributes) { { start_date: "2024-12-19 13:16 UTC", length_in_days: 4 } }
66
+
67
+ it { expect(actual_end_date).to eq(get_date("2024-12-23 13:16 UTC")) }
68
+ end
69
+
70
+ context "when no end date is an empty string and start date with length in days are provided" do
71
+ let(:attributes) { { end_date: "", start_date: "2024-12-19 13:16 UTC", length_in_days: 4 } }
72
+
73
+ it { expect(actual_end_date).to eq(get_date("2024-12-23 13:16 UTC")) }
74
+ end
75
+
76
+ context "when end date is provided" do
77
+ let(:attributes) { { end_date: "2024-12-19 13:16 UTC" } }
78
+
79
+ it { expect(actual_end_date).to eq(get_date("2024-12-19 13:16 UTC")) }
80
+ end
81
+
82
+ context "when end date and length in days are provided" do
83
+ let(:attributes) { { end_date: "2024-12-19 13:16 UTC", length_in_days: 7 } }
84
+
85
+ it do
86
+ expect { actual_end_date }
87
+ .to raise_error(ArgumentError,
88
+ "Should not provide both :end_date (2024-12-19 13:16:00 UTC) " \
89
+ "and :length_in_days (7)!")
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#length_in_days" do
95
+ let(:length_in_days) { sprint_creator_instance.send(:length_in_days) }
96
+
97
+ context "when no :start_date provided" do
98
+ let(:attributes) { { length_in_days: 5 } }
99
+
100
+ it do
101
+ expect { length_in_days }
102
+ .to raise_error(ArgumentError, "Should provide :start_date in order to use :length_in_days!")
103
+ end
104
+ end
105
+
106
+ context "when :start_date provided" do
107
+ let(:start_date_attributes) { { start_date: "2024-12-19 13:16 UTC" } }
108
+
109
+ context "when no length in days is provided" do
110
+ let(:attributes) { start_date_attributes.merge({}) }
111
+
112
+ it { expect(length_in_days).to be_nil }
113
+ end
114
+
115
+ context "when length in days is an empty string" do
116
+ let(:attributes) { start_date_attributes.merge({ length_in_days: "" }) }
117
+
118
+ it { expect(length_in_days).to be_nil }
119
+ end
120
+
121
+ context "when length in days is a number" do
122
+ let(:attributes) { start_date_attributes.merge({ length_in_days: 28 }) }
123
+
124
+ it { expect(length_in_days).to eq(28) }
125
+ end
126
+
127
+ context "when length in days is number in a string" do
128
+ let(:attributes) { start_date_attributes.merge({ length_in_days: "21" }) }
129
+
130
+ it { expect(length_in_days).to eq(21) }
131
+ end
132
+
133
+ context "when length in days is improper integer number in a string" do
134
+ let(:attributes) { start_date_attributes.merge({ length_in_days: "21xx" }) }
135
+
136
+ it { expect { length_in_days }.to raise_error(ArgumentError, 'invalid value for Integer(): "21xx"') }
137
+ end
138
+ end
139
+ end
140
+
141
+ it do
142
+ expect(sprint_creator_instance.send(:request_payload))
143
+ .to eq({
144
+ name: "a_name",
145
+ startDate: "2024-12-19T13:16:00Z",
146
+ endDate: "2025-01-02T13:16:00Z",
147
+ originBoardId: 32
148
+ })
149
+ end
150
+
151
+ describe ".create_sprint" do
152
+ let(:actual_response) do
153
+ instance_double(Net::HTTPResponse, code: "201", body: { "id" => 512 }.to_json)
154
+ end
155
+
156
+ let(:actual_sprints) do
157
+ [
158
+ instance_spy(JIRA::Resource::Sprint, id: 256),
159
+ instance_spy(JIRA::Resource::Sprint, id: 512)
160
+ ]
161
+ end
162
+
163
+ it "returns the object corresponding to the created sprint" do
164
+ allow(jira_client).to receive_messages(send: actual_response)
165
+ allow(jira_client).to receive_messages(Sprint: actual_sprints)
166
+ allow(actual_sprints).to receive(:find).with(512).and_return(actual_sprints.last)
167
+
168
+ expect(described_class.create_sprint(tool, 32,
169
+ { name: "a_name",
170
+ start_date: "2024-12-19 13:16 UTC",
171
+ length_in_days: 14 }))
172
+ .to be_a(Sprint)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/request_builder/sprint_state_updater"
5
+
6
+ RSpec.describe Jira::Auto::Tool::RequestBuilder::SprintStateUpdater do
7
+ let(:sprint_creator_instance) { described_class.new(jira_client, sprint: sprint_to_update, new_state: "closed") }
8
+
9
+ let(:sprint_to_update) do
10
+ instance_spy(JIRA::Resource::Sprint, id: 12_345, attrs: { id: 12_345, state: "open" })
11
+ end
12
+
13
+ let(:jira_client) { instance_spy(JIRA::Client, options: { context_path: :a_context_path }).as_null_object }
14
+
15
+ it { expect(sprint_creator_instance.send(:request_path)).to eq("/rest/agile/1.0/sprint/12345") }
16
+
17
+ it { expect(sprint_creator_instance.send(:http_verb)).to eq(:put) }
18
+
19
+ it do
20
+ expect(sprint_creator_instance.send(:request_payload))
21
+ .to eq({
22
+ id: 123_45,
23
+ self: nil,
24
+ name: nil,
25
+ startDate: nil,
26
+ endDate: nil,
27
+ originBoardId: nil,
28
+ state: "closed"
29
+ })
30
+ end
31
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ RSpec.describe Jira::Auto::Tool::RequestBuilder do
6
+ describe "#run" do
7
+ let(:request_builder) { described_class.new(jira_client) }
8
+
9
+ let(:jira_client) do
10
+ jira_client = instance_spy(JIRA::Client).as_null_object
11
+ allow(JIRA::Client).to receive_messages(new: jira_client)
12
+ jira_client
13
+ end
14
+
15
+ let(:expected_response) do
16
+ instance_double(Net::HTTPResponse, code: 200, body: "sprint updated successfully")
17
+ end
18
+
19
+ describe "#context_path" do
20
+ before do
21
+ allow(jira_client).to receive_messages(options: { context_path: "/path/to/context" })
22
+ end
23
+
24
+ it { expect(request_builder.send(:context_path)).to eq("/path/to/context") }
25
+ end
26
+
27
+ describe "#request_path_with_context" do
28
+ before do
29
+ allow(request_builder).to receive_messages(context_path: "/path/to/context", request_path: "/request/path")
30
+ end
31
+
32
+ it { expect(request_builder.send(:request_path_with_context)).to eq("/path/to/context/request/path") }
33
+ end
34
+
35
+ describe "#run" do
36
+ before do
37
+ allow(jira_client).to receive_messages(put: expected_response)
38
+
39
+ allow(request_builder).to receive_messages(
40
+ http_verb: :put,
41
+ context_path: "/path/to/context", request_path: "/request/path",
42
+ request_payload: { some_payload: "value" },
43
+ expected_response: 200,
44
+ error_message_prefix: "Error updating auto state",
45
+ success_message_prefix: "Sprint state updated successfully"
46
+ )
47
+ end
48
+
49
+ it "sends the expected request" do
50
+ request_builder.run
51
+
52
+ expect(jira_client).to have_received(:put)
53
+ .with("/path/to/context/request/path",
54
+ { some_payload: "value" }.to_json,
55
+ { "Content-Type" => "application/json" })
56
+ end
57
+
58
+ it "returns the response" do
59
+ allow(request_builder).to receive_messages(send_request: expected_response, expected_response: 200)
60
+
61
+ expect(request_builder.run).to eq(expected_response)
62
+ end
63
+ end
64
+
65
+ describe ".build_request_args" do
66
+ it "removes payload as an argument if it is nil" do
67
+ allow(request_builder).to receive_messages(request_headers: :some_headers)
68
+
69
+ expect(request_builder.send(:build_request_args, "/request/path", nil)).to eq(["/request/path", :some_headers])
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/sprint/name"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class Sprint
9
+ RSpec.describe Name do
10
+ describe ".parse" do
11
+ let(:parsed_name) { described_class.parse("ART_Team_24.4.5") }
12
+
13
+ it { expect(parsed_name.prefix).to eq("ART_Team") }
14
+ it { expect(parsed_name.year).to eq(24) }
15
+ it { expect(parsed_name.quarter).to eq(4) }
16
+ it { expect(parsed_name.index_in_quarter).to eq(5) }
17
+ it { expect(parsed_name.planning_interval).to eq([24, 4]) }
18
+
19
+ it "raise an error if the sprint name is not according to convention" do
20
+ expect { described_class.parse("name ignoring naming convention") }
21
+ .to raise_error(Name::NameConventionError,
22
+ "'name ignoring naming convention': " \
23
+ "sprint name not matching #{Name::SPRINT_NAME_REGEX}!")
24
+ end
25
+ end
26
+
27
+ describe "#next_in_planning_interval" do
28
+ let(:parsed_name) { described_class.parse("ART_Team_25.2.1") }
29
+
30
+ it do
31
+ expect(parsed_name.next_in_planning_interval)
32
+ .to eq(described_class.new("ART_Team", 25, 2, 2))
33
+ end
34
+ end
35
+
36
+ # rubocop:disable RSpec/PredicateMatcher
37
+ describe ".respects_naming_convention?" do
38
+ it "returns true if the sprint name is according to convention" do
39
+ expect(described_class.respects_naming_convention?("ART_Team_24.4.5"))
40
+ .to be_truthy
41
+ end
42
+
43
+ it "returns false if the sprint name is not according to convention" do
44
+ expect(described_class.respects_naming_convention?("name ignoring naming convention"))
45
+ .to be_falsey
46
+ end
47
+ end
48
+ # rubocop:enable RSpec/PredicateMatcher
49
+
50
+ describe ".build" do
51
+ it "builds the expected name" do
52
+ expect(described_class.build("ART_Team", 25, 2, 3)).to eq("ART_Team_25.2.3")
53
+ end
54
+ end
55
+
56
+ describe "#new_with" do
57
+ it "builds the expected name" do
58
+ expect(described_class.new_with("ART_Team", "25.3.4"))
59
+ .to eq(described_class.new("ART_Team", 25, 3, 4))
60
+ end
61
+
62
+ it "fails if the sprint name is not according to convention" do
63
+ expect { described_class.new_with("ART_Team", "MALFORMED_SUFFIX") }
64
+ .to raise_error(
65
+ Name::NameConventionError,
66
+ "suffix not following convention 'MALFORMED_SUFFIX': " \
67
+ "resulting sprint name 'ART_Team_MALFORMED_SUFFIX' not matching #{Name::SPRINT_NAME_REGEX}!"
68
+ )
69
+ end
70
+ end
71
+
72
+ describe "#<=>" do
73
+ let(:parsed_name) { described_class.parse("ART_Team_24.4.5") }
74
+
75
+ it { expect(parsed_name).to be > described_class.parse("ART_Team_24.4.1") }
76
+ it { expect(parsed_name).to eq described_class.parse("ART_Team_24.4.05") }
77
+ it { expect(parsed_name).to be < described_class.parse("ART_Team_24.4.10") }
78
+
79
+ context "when comparing to a sprint name not according to convention" do
80
+ it { expect(parsed_name).to be > "1st sprint" }
81
+ it { expect(parsed_name).to be < "name ignoring naming convention" }
82
+ end
83
+ end
84
+
85
+ describe "#comparison_values" do
86
+ let(:parsed_name) { described_class.parse("ART_Team_24.4.5") }
87
+
88
+ it { expect(parsed_name.send(:comparison_values, parsed_name)).to eq(["ART_Team", 24, 4, 5]) }
89
+
90
+ context "when sprint name not according to convention" do
91
+ it {
92
+ expect(parsed_name.send(:comparison_values,
93
+ "name not following conventions")).to eq(["name not following conventions"])
94
+ }
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/sprint/prefix"
4
+ require "jira/auto/tool/until_date"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ class Sprint
10
+ RSpec.describe Prefix do
11
+ def new_prefix(name, sprints = [])
12
+ described_class.new(name, sprints)
13
+ end
14
+
15
+ describe "#name" do
16
+ it "returns the name of the prefix" do
17
+ expect(new_prefix("ART_Team").name).to eq("ART_Team")
18
+ end
19
+ end
20
+
21
+ describe "#sprints" do
22
+ it "is empty by default" do
23
+ expect(described_class.new("a_prefix").sprints).to be_empty
24
+ end
25
+
26
+ it "can be specified explicitly at creation" do
27
+ expect(new_prefix("ART_Another-Team", %w[sprint_1 sprint_2]).sprints)
28
+ .to eq(%w[sprint_1 sprint_2])
29
+ end
30
+
31
+ it "accepts new sprints" do
32
+ prefix = new_prefix("ART_Team")
33
+ prefix << :one_sprint
34
+ prefix << :another_sprint
35
+
36
+ expect(prefix.sprints).to eq(%i[another_sprint one_sprint])
37
+ end
38
+
39
+ context "when looking at the sprint order" do
40
+ let(:prefix) { new_prefix("ART_Team", %i[sprint_comes_2nd sprint_comes_1st]) }
41
+
42
+ it "has sorted sprints" do
43
+ expect(prefix.sprints).to eq(%i[sprint_comes_1st sprint_comes_2nd])
44
+ end
45
+
46
+ it "stays sorted when new sprints are added" do
47
+ prefix << :before_2nd_sprint
48
+ prefix << :before_1st_sprint
49
+
50
+ expect(prefix.sprints).to eq(%i[before_1st_sprint before_2nd_sprint sprint_comes_1st sprint_comes_2nd])
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "#to_table_row" do
56
+ let(:prefix) { new_prefix("Food_Supply", []) }
57
+
58
+ let(:last_sprint) { instance_double(Sprint) }
59
+
60
+ before { allow(prefix).to receive_messages(last_sprint: last_sprint) }
61
+
62
+ it do
63
+ allow(last_sprint)
64
+ .to receive_messages(to_table_row:
65
+ [512, "Food_Supply_24.4.5", 4, "2024-12-27 13:00 UTC", "2024-12-31 13:00 UTC",
66
+ "Food Supply Team Board", :url_to_suppply_team_board, "FOOD"])
67
+
68
+ expect(prefix.to_table_row)
69
+ .to eq(["Food_Supply", 512, "Food_Supply_24.4.5", 4, "2024-12-27 13:00 UTC", "2024-12-31 13:00 UTC",
70
+ "Food Supply Team Board", :url_to_suppply_team_board, "FOOD"])
71
+ end
72
+
73
+ it "can exclude the board information" do
74
+ allow(last_sprint)
75
+ .to receive(:to_table_row)
76
+ .with(without_board_information: true)
77
+ .and_return([512, "Food_Supply_24.4.5", 4, "2024-12-27 13:00 UTC", "2024-12-31 13:00 UTC"])
78
+
79
+ expect(prefix.to_table_row(without_board_information: true))
80
+ .to eq(["Food_Supply", 512, "Food_Supply_24.4.5", 4, "2024-12-27 13:00 UTC", "2024-12-31 13:00 UTC"])
81
+ end
82
+ end
83
+
84
+ describe ".to_table_row_header" do
85
+ it do
86
+ allow(Sprint)
87
+ .to receive_messages(to_table_row_header: ["Id", "Name", "Length In Days", "Start Date", "End Date",
88
+ "Board Name", "Board UI URL", "Board Project Key"])
89
+
90
+ expect(described_class.to_table_row_header)
91
+ .to eq(["Sprint Prefix", "Last Sprint Id", "Last Sprint Name", "Length In Days",
92
+ "Start Date", "End Date", "Board Name", "Board UI URL", "Board Project Key"])
93
+ end
94
+
95
+ it "can exclude the board information" do
96
+ allow(Sprint)
97
+ .to receive(:to_table_row_header).with(without_board_information: true)
98
+ .and_return(["Id", "Name", "Length In Days", "Start Date", "End Date"])
99
+
100
+ expect(described_class.to_table_row_header(without_board_information: true))
101
+ .to eq(["Sprint Prefix", "Last Sprint Id", "Last Sprint Name", "Length In Days", "Start Date",
102
+ "End Date"])
103
+ end
104
+ end
105
+
106
+ describe "#<=>" do
107
+ let(:art_team_prefix) { new_prefix("ART_Team", %w[sprint_1 sprint_2]) }
108
+
109
+ it { expect(art_team_prefix).to eq(new_prefix("ART_Team", %w[sprint_1 sprint_2])) }
110
+ it { expect(art_team_prefix).to be > new_prefix("ART_Team", %w[sprint_1]) }
111
+ it { expect(art_team_prefix).to be > new_prefix("ART_Team", %w[p1 p2]) }
112
+ it { expect(art_team_prefix).to be < new_prefix("ART_Team", %w[x1]) }
113
+ it { expect(art_team_prefix).to be < new_prefix("GreaterART_Team", []) }
114
+ it { expect(art_team_prefix).to be > new_prefix("AA_Team", []) }
115
+ end
116
+
117
+ describe "#add_sprint_following_last_one" do
118
+ context "when unclosed sprint are found" do
119
+ let(:actual_sprints) do
120
+ [
121
+ "1st sprint",
122
+ "last sprint",
123
+ "2nd sprint"
124
+ ]
125
+ end
126
+
127
+ let(:prefix) { described_class.new("sprint", actual_sprints) }
128
+ let(:newly_added_sprint) { instance_double(Sprint, name: "sprint following last sprint") }
129
+
130
+ it "add a sprint for the sprint prefixes having at least one unclosed sprint" do
131
+ allow(NextSprintCreator)
132
+ .to receive(:create_sprint_following).with("last sprint").and_return(newly_added_sprint)
133
+ allow(prefix).to receive_messages(:<< => nil)
134
+
135
+ prefix.add_sprint_following_last_one
136
+
137
+ expect(prefix).to have_received(:<<).with(newly_added_sprint)
138
+ end
139
+ end
140
+ end
141
+
142
+ describe "#quarterly_add_sprints_until" do
143
+ let(:until_date) { UntilDate.new("2024-05-15") }
144
+ let(:prefix) { described_class.new("name_prefix") }
145
+
146
+ it "adds no sprint if date is already covered" do
147
+ allow(prefix).to receive_messages(covered?: true, add_sprint_following_last_one: nil)
148
+
149
+ prefix.quarterly_add_sprints_until(until_date)
150
+
151
+ expect(prefix).not_to have_received(:add_sprint_following_last_one)
152
+ end
153
+
154
+ it "adds sprints until date is covered" do
155
+ allow(prefix).to receive(:covered?).and_return(false, false, true)
156
+ allow(prefix).to receive_messages(add_sprint_following_last_one: nil)
157
+
158
+ prefix.quarterly_add_sprints_until(until_date)
159
+
160
+ expect(prefix).to have_received(:add_sprint_following_last_one).exactly(:twice)
161
+ end
162
+ end
163
+
164
+ describe "#covered?" do
165
+ let(:last_sprint) do
166
+ instance_double(Sprint,
167
+ start_date: Time.parse("2024-05-01 11:00 UTC"),
168
+ end_date: Time.parse("2024-05-14 11:00 UTC"))
169
+ end
170
+
171
+ let(:prefix) { described_class.new("name_prefix") }
172
+
173
+ before { allow(prefix).to receive_messages(last_sprint: last_sprint) }
174
+
175
+ it "handles date anterior to last sprint" do
176
+ expect(prefix_has_covered?("2024-04-30 11:00 UTC")).to be true
177
+ end
178
+
179
+ it "handles date included in the last sprint" do
180
+ expect(prefix_has_covered?("2024-05-01 11:00 UTC")).to be true
181
+ end
182
+
183
+ it "handles time past to the last sprint end time on the same day" do
184
+ expect(prefix_has_covered?("2024-05-14 12:00 UTC")).to be false
185
+ end
186
+
187
+ it "handles date past to the last sprint day" do
188
+ expect(prefix_has_covered?("2024-05-15 11:00 UTC")).to be false
189
+ end
190
+
191
+ def prefix_has_covered?(date_string)
192
+ prefix.covered?(UntilDate.new(date_string))
193
+ end
194
+ end
195
+
196
+ describe "#last_sprint" do
197
+ it "returns the last sprint as per the sprint default sort criteria" do
198
+ prefix = new_prefix("ART_Team", %w[sprint_2 xx_last_sprint sprint_1 another_sprint1])
199
+
200
+ expect(prefix.last_sprint).to eq("xx_last_sprint")
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end