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,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/performer/sprint_renamer"
5
+
6
+ RSpec.describe Jira::Auto::Tool::Performer::SprintRenamer do
7
+ let(:sprint_renamer) { described_class.new(tool, from_string, to_string) }
8
+ let(:tool) { instance_double(Jira::Auto::Tool) }
9
+ let(:from_string) { "25.1.5" }
10
+ let(:to_string) { "25.2.1" }
11
+
12
+ describe "#from_string_regex" do
13
+ it { expect(sprint_renamer.from_string_regex).to eq(/25\.1\.5/) }
14
+ end
15
+
16
+ describe "#to_string" do
17
+ it { expect(sprint_renamer.to_string).to eq("25.2.1") }
18
+ end
19
+
20
+ describe "#acton_sprints_for_sprint_prefix" do
21
+ def create_sprint(name)
22
+ instance_double(Jira::Auto::Tool::Sprint, name: name, rename_to: nil)
23
+ end
24
+
25
+ let(:one_sprint) { create_sprint("a sprint name") }
26
+ let(:another_sprint) { create_sprint("another sprint name") }
27
+
28
+ let(:sprints) { [one_sprint, another_sprint] }
29
+
30
+ let(:prefix) { instance_double(Jira::Auto::Tool::Sprint::Prefix, name: "Food_Delivery", sprints: sprints) }
31
+
32
+ before do
33
+ allow(sprint_renamer).to receive_messages(calculate_sprint_new_names: ["a new name", "another new name"])
34
+ end
35
+
36
+ it "renames each sprint" do
37
+ sprint_renamer.act_on_sprints_for_sprint_prefix(prefix)
38
+
39
+ expect(sprints).to all have_received(:rename_to)
40
+ end
41
+ end
42
+
43
+ describe "#calculate_sprint_new_names" do
44
+ let(:sprint_names) do
45
+ %w[
46
+ Food_Delivery_25.1.2
47
+ Food_Delivery_25.1.3
48
+ Food_Delivery_25.1.4
49
+ Food_Delivery_25.1.5
50
+ Food_Delivery_25.2.1
51
+ Food_Delivery_25.2.2
52
+ Food_Delivery_25.2.3
53
+ Food_Delivery_25.2.4
54
+ Food_Delivery_25.2.5
55
+ ]
56
+ end
57
+
58
+ context "when pulling a sprint into the previous planning interval" do
59
+ let(:from_string) { "25.2.1" }
60
+ let(:to_string) { "25.1.6" }
61
+
62
+ let(:expected_new_sprint_names) do
63
+ %w[
64
+ Food_Delivery_25.1.2
65
+ Food_Delivery_25.1.3
66
+ Food_Delivery_25.1.4
67
+ Food_Delivery_25.1.5
68
+ Food_Delivery_25.1.6
69
+ Food_Delivery_25.1.7
70
+ Food_Delivery_25.1.8
71
+ Food_Delivery_25.1.9
72
+ Food_Delivery_25.1.10
73
+ ]
74
+ end
75
+
76
+ it { expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names) }
77
+ end
78
+
79
+ context "when pushing a sprint to the next planning interval" do
80
+ let(:from_string) { "25.1.5" }
81
+ let(:to_string) { "25.2.1" }
82
+
83
+ let(:expected_new_sprint_names) do
84
+ %w[
85
+ Food_Delivery_25.1.2
86
+ Food_Delivery_25.1.3
87
+ Food_Delivery_25.1.4
88
+ Food_Delivery_25.2.1
89
+ Food_Delivery_25.2.2
90
+ Food_Delivery_25.2.3
91
+ Food_Delivery_25.2.4
92
+ Food_Delivery_25.2.5
93
+ Food_Delivery_25.2.6
94
+ ]
95
+ end
96
+
97
+ it { expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names) }
98
+ end
99
+
100
+ context "when renaming sprint forward inside planning interval" do
101
+ let(:from_string) { "25.2.1" }
102
+ let(:to_string) { "25.2.10" }
103
+
104
+ let(:expected_new_sprint_names) do
105
+ %w[
106
+ Food_Delivery_25.1.2
107
+ Food_Delivery_25.1.3
108
+ Food_Delivery_25.1.4
109
+ Food_Delivery_25.1.5
110
+ Food_Delivery_25.2.10
111
+ Food_Delivery_25.2.11
112
+ Food_Delivery_25.2.12
113
+ Food_Delivery_25.2.13
114
+ Food_Delivery_25.2.14
115
+ ]
116
+ end
117
+
118
+ it do
119
+ expect(sprint_renamer.calculate_sprint_new_names(["Food_Delivery_25.1.5"]))
120
+ .to eq(["Food_Delivery_25.1.5"])
121
+ end
122
+
123
+ it { expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names) }
124
+ end
125
+
126
+ context "when renaming sprint backward inside planning interval" do
127
+ let(:from_string) { "25.2.10" }
128
+ let(:to_string) { "25.2.1" }
129
+
130
+ let(:sprint_names) do
131
+ %w[
132
+ Food_Delivery_25.1.2
133
+ Food_Delivery_25.1.3
134
+ Food_Delivery_25.1.4
135
+ Food_Delivery_25.1.5
136
+ Food_Delivery_25.2.10
137
+ Food_Delivery_25.2.11
138
+ Food_Delivery_25.2.12
139
+ Food_Delivery_25.2.13
140
+ Food_Delivery_25.2.14
141
+ ]
142
+ end
143
+
144
+ let(:expected_new_sprint_names) do
145
+ %w[
146
+ Food_Delivery_25.1.2
147
+ Food_Delivery_25.1.3
148
+ Food_Delivery_25.1.4
149
+ Food_Delivery_25.1.5
150
+ Food_Delivery_25.2.1
151
+ Food_Delivery_25.2.2
152
+ Food_Delivery_25.2.3
153
+ Food_Delivery_25.2.4
154
+ Food_Delivery_25.2.5
155
+ ]
156
+ end
157
+
158
+ it do
159
+ expect(sprint_renamer.calculate_sprint_new_names(["Food_Delivery_25.1.5"]))
160
+ .to eq(["Food_Delivery_25.1.5"])
161
+ end
162
+
163
+ it { expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names) }
164
+ end
165
+
166
+ context "when sprints exist beyond the immediate planning interval of the sprint" do
167
+ let(:from_string) { "25.2.1" }
168
+ let(:to_string) { "25.1.6" }
169
+
170
+ let(:sprint_names) do
171
+ %w[
172
+ Food_Delivery_25.1.5
173
+ Food_Delivery_25.2.1
174
+ Food_Delivery_25.2.2
175
+ Food_Delivery_25.3.1
176
+ Food_Delivery_25.3.2
177
+ Food_Delivery_25.3.3
178
+ ]
179
+ end
180
+
181
+ let(:expected_new_sprint_names) do
182
+ %w[
183
+ Food_Delivery_25.1.5
184
+ Food_Delivery_25.1.6
185
+ Food_Delivery_25.1.7
186
+ Food_Delivery_25.1.8
187
+ Food_Delivery_25.1.9
188
+ Food_Delivery_25.1.10
189
+ ]
190
+ end
191
+
192
+ it "also renames those sprints" do
193
+ expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names)
194
+ end
195
+ end
196
+
197
+ context "when multiple sprints are matching the first sprint to rename" do
198
+ let(:from_string) { "25.2.1" }
199
+ let(:to_string) { "25.1.6" }
200
+
201
+ let(:sprint_names) do
202
+ %w[
203
+ Food_Delivery_25.1.5
204
+ Food_Delivery_25.2.1
205
+ Food_Delivery_25.2.1
206
+ Food_Delivery_25.2.1
207
+ Food_Delivery_25.2.1
208
+ ]
209
+ end
210
+
211
+ let(:expected_new_sprint_names) do
212
+ %w[
213
+ Food_Delivery_25.1.5
214
+ Food_Delivery_25.1.6
215
+ Food_Delivery_25.1.7
216
+ Food_Delivery_25.1.8
217
+ Food_Delivery_25.1.9
218
+ ]
219
+ end
220
+
221
+ it "does rename those sprints in sequence" do
222
+ expect(sprint_renamer.calculate_sprint_new_names(sprint_names)).to eq(expected_new_sprint_names)
223
+ end
224
+ end
225
+ end
226
+
227
+ describe "#first_sprint_to_act_on?" do
228
+ let(:sprint_renamer) { described_class.new(nil, "25.1.5", "25.2.1") }
229
+
230
+ def first_to_rename?(sprint_name)
231
+ sprint_renamer.first_sprint_to_act_on?(sprint_name)
232
+ end
233
+
234
+ it { expect(first_to_rename?("prefix_25.1.4")).not_to be_truthy }
235
+ it { expect(first_to_rename?("prefix_25.1.5")).to be_truthy }
236
+ it { expect(first_to_rename?("prefix_24.1.5")).not_to be_truthy }
237
+ it { expect(first_to_rename?("prefix_25.2.1")).not_to be_truthy }
238
+ it { expect(first_to_rename?("prefix_26.1.6")).not_to be_truthy }
239
+ end
240
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/performer/sprint_time_in_dates_aligner"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ class Performer
10
+ class SprintTimeInDatesAligner
11
+ RSpec.describe SprintTimeInDatesAligner do
12
+ let(:sprint_in_dates_aligner) { described_class.new(tool, "12:31 +0100") }
13
+ let(:tool) { instance_double(Tool) }
14
+
15
+ describe "#run" do
16
+ it do
17
+ allow(sprint_in_dates_aligner).to receive_messages(update_sprint_dates_with_expected_time: nil)
18
+
19
+ sprint_in_dates_aligner.run
20
+
21
+ expect(sprint_in_dates_aligner).to have_received(:update_sprint_dates_with_expected_time)
22
+ end
23
+ end
24
+
25
+ describe "#update_sprint_dates_with_expected_time" do
26
+ let(:unclosed_sprints) { %i[a_sprint another_sprint] }
27
+
28
+ it "updates unclosed sprints" do
29
+ allow(tool).to receive_messages(unclosed_sprints: unclosed_sprints)
30
+ allow(sprint_in_dates_aligner).to receive_messages(update_sprint_dates_for: nil)
31
+
32
+ sprint_in_dates_aligner.update_sprint_dates_with_expected_time
33
+
34
+ expect(sprint_in_dates_aligner).to have_received(:update_sprint_dates_for).once.with(:a_sprint)
35
+ expect(sprint_in_dates_aligner).to have_received(:update_sprint_dates_for).once.with(:another_sprint)
36
+ end
37
+ end
38
+
39
+ describe "#sprint_time_in_dates" do
40
+ it { expect(sprint_in_dates_aligner.sprint_time_in_dates).to be_a(Time) }
41
+ end
42
+
43
+ describe("#update_sprint_dates_for") do
44
+ context "when sprint is closed" do
45
+ let(:sprint) do
46
+ instance_double(Sprint, closed?: true, save: nil)
47
+ end
48
+
49
+ it do
50
+ sprint_in_dates_aligner.update_sprint_dates_for(sprint)
51
+
52
+ expect(sprint).not_to have_received(:save)
53
+ end
54
+ end
55
+
56
+ context "when sprint is not closed" do
57
+ let(:sprint) { Sprint.new(nil, nil) }
58
+ let(:jira_sprint) do
59
+ instance_double(JIRA::Resource::Sprint, attrs: { "startdate" => start_date, "enddate" => end_date })
60
+ end
61
+
62
+ before do
63
+ allow(sprint).to receive_messages(closed?: false,
64
+ jira_sprint: jira_sprint,
65
+ save: nil,
66
+ start_date: start_date,
67
+ end_date: end_date,
68
+ end_date?: end_date != Sprint::UNDEFINED_DATE,
69
+ start_date?: start_date != Sprint::UNDEFINED_DATE)
70
+ allow(sprint).to receive(:start_date=).and_call_original
71
+ allow(sprint).to receive(:end_date=).and_call_original
72
+ end
73
+
74
+ def get_date(date_string)
75
+ DateTime.parse(date_string)
76
+ end
77
+
78
+ context "when start and end date specified" do
79
+ let(:start_date) { get_date("2025-02-08 14:00 UTC") }
80
+ let(:end_date) { get_date("2025-02-10 14:00 UTC") }
81
+
82
+ it do
83
+ expect(sprint).to receive(:start_date=).with(get_date("2025-02-08 12:31:00 +0100"))
84
+ expect(sprint).to receive(:end_date=).with(get_date("2025-02-10 12:31:00 +0100"))
85
+ expect(sprint).to receive(:save).once
86
+
87
+ sprint_in_dates_aligner.update_sprint_dates_for(sprint)
88
+ end
89
+ end
90
+
91
+ context "when start and end date are unspecified" do
92
+ let(:start_date) { Sprint::UNDEFINED_DATE }
93
+ let(:end_date) { Sprint::UNDEFINED_DATE }
94
+
95
+ it do
96
+ sprint_in_dates_aligner.update_sprint_dates_for(sprint)
97
+
98
+ expect(sprint).not_to have_received(:save)
99
+ end
100
+ end
101
+
102
+ context "when start_date is unspecified" do
103
+ let(:start_date) { Sprint::UNDEFINED_DATE }
104
+ let(:end_date) { get_date("2025-02-14 15:00 UTC") }
105
+
106
+ it do
107
+ expect(sprint).to receive(:end_date=).with(get_date("2025-02-14 12:31:00 +0100"))
108
+ expect(sprint).to receive(:save).once
109
+
110
+ sprint_in_dates_aligner.update_sprint_dates_for(sprint)
111
+ end
112
+ end
113
+
114
+ context "when end_date is unspecified" do
115
+ let(:start_date) { get_date("2025-02-28 05:00 UTC") }
116
+ let(:end_date) { Sprint::UNDEFINED_DATE }
117
+
118
+ it do
119
+ expect(sprint).to receive(:start_date=).with(get_date("2025-02-28T12:31:00 +0100"))
120
+ expect(sprint).to receive(:save).once
121
+
122
+ sprint_in_dates_aligner.update_sprint_dates_for(sprint)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end