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,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool"
4
+ require_relative "helpers/environment_based_value"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ class Ticket
10
+ include Helpers::EnvironmentBasedValue
11
+
12
+ attr_reader :tool, :jira_ticket
13
+
14
+ def initialize(tool, jira_ticket, implementation_team = nil, expected_start_date = nil)
15
+ @tool = tool
16
+ @jira_ticket = jira_ticket
17
+ @implementation_team = implementation_team
18
+ @expected_start_date = expected_start_date
19
+ end
20
+
21
+ def key
22
+ jira_ticket.key
23
+ end
24
+
25
+ def sprint=(sprint)
26
+ @sprint = sprint
27
+
28
+ jira_ticket.save!({ "fields" => { tool.jira_sprint_field.id => sprint.id } })
29
+ rescue StandardError => e
30
+ message = "Failed to set sprint for ticket #{key} to #{sprint.id}! #{self}"
31
+
32
+ log.error { message }
33
+
34
+ raise e.class, message
35
+ end
36
+
37
+ def to_s
38
+ "Ticket(#{to_s_fields.collect { |field| "#{field}: #{send(field)}" }.join(", ")})"
39
+ end
40
+
41
+ def to_s_fields
42
+ %i[key summary sprint implementation_team expected_start_date]
43
+ end
44
+
45
+ def sprint
46
+ jira_ticket.fields.fetch(tool.jira_sprint_field.id)
47
+ end
48
+
49
+ def jira_client
50
+ tool.jira_client
51
+ end
52
+
53
+ def jira_sprint_field
54
+ tool.jira_sprint_field
55
+ end
56
+
57
+ def expected_start_date
58
+ @expected_start_date || jira_field_value(expected_start_date_field.id)
59
+ end
60
+
61
+ IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES = %w[value name].freeze
62
+
63
+ def implementation_team
64
+ @implementation_team ||=
65
+ begin
66
+ attributes = implementation_team_attributes
67
+
68
+ if attributes.nil?
69
+ nil
70
+ else
71
+ IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES.any? { |attr| attributes.key?(attr) } or
72
+ raise "Implementation team #{IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES.join(" and ")} " \
73
+ "attributes not found in #{attributes}!"
74
+
75
+ attributes.values_at(*IMPLEMENTATION_TEAM_VALUE_ATTRIBUTES).compact.first
76
+ end
77
+ end
78
+ end
79
+
80
+ def implementation_team_attributes
81
+ jira_field_value(implementation_team_field.id)
82
+ end
83
+
84
+ def implementation_team_field
85
+ tool.implementation_team_field
86
+ end
87
+
88
+ def expected_start_date_field
89
+ tool.expected_start_date_field
90
+ end
91
+
92
+ def summary
93
+ jira_ticket.summary
94
+ end
95
+
96
+ def jira_field_value(field_id = caller_locations(1, 1).first.base_label)
97
+ log.debug { "jira_field_value(#{field_id})" }
98
+
99
+ field = jira_ticket.fields.fetch(field_id) do |id|
100
+ raise "#{id}: value not found in #{jira_ticket.fields}"
101
+ end
102
+
103
+ log.debug { "jira_field_value(#{field_id}), field: #{field}" }
104
+
105
+ field
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/helpers/overridable_time"
4
+ require_relative "request_builder/sprint_creator"
5
+ require_relative "request_builder/sprint_state_updater"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class UntilDate
11
+ class FormatError < RuntimeError; end
12
+
13
+ attr_reader :time
14
+
15
+ NAMED_DATES = %i[
16
+ today
17
+ current_quarter
18
+ coming_quarter
19
+ ].freeze
20
+
21
+ NAMED_DATE_REGEX = /^(#{NAMED_DATES.join("|")})$/
22
+
23
+ INCLUDES_TIME_REGEX = /.+\s\d{2}:\d{2}.+/
24
+ STARTS_WITH_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ # TODO : use something more reliable like the Chronic gem
25
+ TIME_UNTIL_MIDNIGHT_UTC = " 23:59:59 UTC"
26
+
27
+ def initialize(date_string)
28
+ @time =
29
+ case date_string
30
+ when NAMED_DATE_REGEX
31
+ send(date_string.intern)
32
+ else
33
+ parse_date(date_string)
34
+ end
35
+ end
36
+
37
+ def current_date_time
38
+ Helpers::OverridableTime.now.utc
39
+ end
40
+
41
+ private
42
+
43
+ def parse_date(date_string)
44
+ case date_string
45
+ when INCLUDES_TIME_REGEX
46
+ Time.parse(date_string)
47
+ when STARTS_WITH_DATE_REGEX
48
+ Time.parse(date_string + TIME_UNTIL_MIDNIGHT_UTC).end_of_day
49
+ else
50
+ raise FormatError, "date string '#{date_string}' is not in a supported format"
51
+ end
52
+ end
53
+
54
+ def today
55
+ current_date_time.end_of_day
56
+ end
57
+
58
+ def current_quarter
59
+ today.end_of_quarter
60
+ end
61
+
62
+ def coming_quarter
63
+ (current_quarter + 1.day).end_of_quarter
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jira
4
+ module Auto
5
+ class Tool
6
+ VERSION = "0.1.1"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/blank"
4
+ require "date"
5
+ require "active_support/core_ext/numeric/time"
6
+ require "active_support/core_ext/date/calculations"
7
+ require "jira-ruby"
8
+
9
+ require_relative "tool/config"
10
+ require_relative "tool/board_controller"
11
+ require_relative "tool/helpers/environment_based_value"
12
+ require_relative "tool/project"
13
+ require_relative "tool/rate_limited_jira_client"
14
+ require_relative "tool/request_builder"
15
+ require_relative "tool/setup_logging"
16
+ require_relative "tool/sprint_controller"
17
+ require_relative "tool/sprint_state_controller"
18
+ require_relative "tool/team"
19
+ require_relative "tool/team_sprint_prefix_mapper"
20
+ require_relative "tool/team_sprint_ticket_dispatcher"
21
+ require_relative "tool/ticket"
22
+ require_relative "tool/version"
23
+
24
+ module Jira
25
+ module Auto
26
+ class Tool
27
+ extend Helpers::EnvironmentBasedValue
28
+
29
+ class Error < StandardError; end
30
+
31
+ def config
32
+ @config ||= Config.new(self)
33
+ end
34
+
35
+ def board_name
36
+ jira_board_name
37
+ end
38
+
39
+ def board
40
+ found_board = boards.find { |a_board| a_board.name == board_name } or
41
+ raise KeyError, "Board '#{board_name}' not found!"
42
+
43
+ log.debug { "Jira board '#{board_name}' found: #{found_board}!" }
44
+
45
+ found_board
46
+ end
47
+
48
+ def boards
49
+ board_controller.boards
50
+ end
51
+
52
+ def fetch_sprint(sprint_name)
53
+ sprint_controller.sprints.find { |sprint| sprint.name == sprint_name } or
54
+ raise KeyError, "Sprint '#{sprint_name}' not found for #{board.name}!" # TODO: - test this condition
55
+ end
56
+
57
+ ATTRIBUTES_TO_IGNORE_FOR_SPRINT_CREATION = %i[state].freeze
58
+
59
+ def create_sprint(attributes)
60
+ attributes_for_sprint_creation = attributes.except(*ATTRIBUTES_TO_IGNORE_FOR_SPRINT_CREATION)
61
+
62
+ created_sprint = create_future_sprint(attributes_for_sprint_creation)
63
+
64
+ log.debug { created_sprint.inspect }
65
+
66
+ transition_sprint_state(created_sprint, desired_state: attributes.fetch(:state))
67
+ end
68
+
69
+ def transition_sprint_state(created_sprint, desired_state:)
70
+ SprintStateController.new(jira_client, created_sprint).transition_to(desired_state)
71
+ end
72
+
73
+ def jira_client
74
+ RateLimitedJiraClient.new(jira_client_options,
75
+ rate_interval:
76
+ jat_rate_interval_in_seconds_when_defined_else(
77
+ RateLimitedJiraClient::NO_RATE_INTERVAL_IN_SECONDS
78
+ ).to_i,
79
+ rate_limit:
80
+ jat_rate_limit_in_seconds_when_defined_else(
81
+ RateLimitedJiraClient::NO_RATE_LIMIT_IN_SECONDS
82
+ ).to_i)
83
+ end
84
+
85
+ def jira_client_options
86
+ {
87
+ username: jira_username,
88
+ password: jira_api_token,
89
+ site: jira_site_url,
90
+ context_path: jira_context_path_when_defined_else(""),
91
+ auth_type: :basic,
92
+ http_debug: jira_http_debug?
93
+ }
94
+ end
95
+
96
+ def jira_http_debug?
97
+ value = if config.key?(:jira_http_debug)
98
+ config[:jira_http_debug]
99
+ else
100
+ jira_http_debug_defined? && jira_http_debug
101
+ end
102
+
103
+ result = case value
104
+ when String
105
+ value =~ /^(true|yes|1)$/i
106
+ else
107
+ value
108
+ end
109
+
110
+ log.debug { "jira_http_debug? = #{result} (jira_http_debug = #{value}, config = #{config.inspect})" }
111
+
112
+ result
113
+ end
114
+
115
+ def jira_request_path(path)
116
+ jira_client.options[:context_path] + path
117
+ end
118
+
119
+ def jira_base_url
120
+ jira_client.options[:site] + jira_client.options[:context_path]
121
+ end
122
+
123
+ def jira_url(url)
124
+ jira_base_url + url
125
+ end
126
+
127
+ %i[
128
+ art_sprint_regex
129
+ expected_start_date_field_name
130
+ implementation_team_field_name
131
+ jat_rate_limit_in_seconds
132
+ jat_rate_interval_in_seconds
133
+ jat_tickets_for_team_sprint_ticket_dispatcher_jql
134
+ jira_api_token
135
+ jira_board_name
136
+ jira_board_name_regex
137
+ jira_context_path
138
+ jira_http_debug
139
+ jira_project_key
140
+ jira_site_url
141
+ jira_username
142
+ jira_sprint_field_name
143
+ ].each do |method_name|
144
+ define_overridable_environment_based_value(method_name)
145
+ end
146
+
147
+ def board_controller
148
+ @board_controller ||= BoardController.new(self)
149
+ end
150
+
151
+ def sprint_controller
152
+ @sprint_controller ||= SprintController.new(self, board)
153
+ end
154
+
155
+ def project_ticket_fields
156
+ project.ticket_fields
157
+ end
158
+
159
+ def project
160
+ @project ||= Project.find(self, jira_project_key)
161
+ end
162
+
163
+ def expected_start_date_field(field_name = expected_start_date_field_name)
164
+ field_controller.expected_start_date_field(field_name)
165
+ end
166
+
167
+ def implementation_team_field(field_name = implementation_team_field_name)
168
+ field_controller.implementation_team_field(field_name)
169
+ end
170
+
171
+ def jira_sprint_field(field_name = jira_sprint_field_name)
172
+ field_controller.sprint_field(field_name)
173
+ end
174
+
175
+ def field_controller
176
+ @field_controller ||= FieldController.new(jira_client)
177
+ end
178
+
179
+ def unclosed_sprints
180
+ sprint_controller.unclosed_sprints
181
+ end
182
+
183
+ def unclosed_sprint_prefixes
184
+ sprint_controller.unclosed_sprint_prefixes
185
+ end
186
+
187
+ def teams
188
+ implementation_team_field.values.collect { |value| Team.new(value) }.collect(&:name)
189
+ end
190
+
191
+ def tickets(jql = "project = #{project.key}")
192
+ jira_client.Issue.jql(jql).collect { |jira_ticket| Ticket.new(self, jira_ticket) }
193
+ rescue StandardError => e
194
+ raise <<~EOEM
195
+ Error fetching project tickets: Something went wrong:
196
+ __________ Please check your Jira configuration and your query ______
197
+ jql = #{jql}
198
+ #{e.class}: #{e.message}
199
+ EOEM
200
+ end
201
+
202
+ def team_sprint_ticket_dispatcher
203
+ TeamSprintTicketDispatcher.new(jira_client,
204
+ tickets(jat_tickets_for_team_sprint_ticket_dispatcher_jql),
205
+ unclosed_sprint_prefixes)
206
+ end
207
+
208
+ private
209
+
210
+ def create_future_sprint(attributes)
211
+ RequestBuilder::SprintCreator
212
+ .create_sprint(self, board.id, attributes)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,8 @@
1
+ module Jira
2
+ module Auto
3
+ module Tool
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ require "jira/auto/tool/board/cache"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class Board
11
+ class Cache
12
+ RSpec.describe Cache do
13
+ let(:cache) { described_class.new(tool) }
14
+ let(:tool) { instance_double(Tool, jira_client: jira_client) }
15
+ let(:jira_client) { jira_resource_double(JIRA::Client) }
16
+ let(:board_factory) { double("BoardFactory") } # rubocop:disable RSpec/VerifiedDoubles
17
+
18
+ describe "#boards" do
19
+ before { allow(cache).to receive_messages(valid?: valid?) }
20
+
21
+ context "when cache is valid" do
22
+ let(:valid?) { true }
23
+
24
+ let(:raw_content) do
25
+ {
26
+ "boards" => [
27
+ { "id" => 16, "name" => "name for board #16" },
28
+ { "id" => 32, "name" => "name for board #32" }
29
+ ]
30
+ }
31
+ end
32
+
33
+ before do
34
+ allow(cache).to receive_messages(raw_content: raw_content)
35
+ allow(jira_client).to receive_messages(Board: board_factory)
36
+
37
+ allow(board_factory)
38
+ .to receive(:build).with({ "id" => 16, "name" => "name for board #16" })
39
+ .and_return(:first_jira_board)
40
+
41
+ allow(Board).to receive(:new).with(tool, :first_jira_board).and_return(:first_board)
42
+
43
+ allow(board_factory)
44
+ .to receive(:build).with({ "id" => 32, "name" => "name for board #32" })
45
+ .and_return(:second_jira_board)
46
+
47
+ allow(Board).to receive(:new).with(tool, :second_jira_board).and_return(:second_board)
48
+ end
49
+
50
+ it "returns the boards" do
51
+ expect(cache.boards).to eq(%i[first_board second_board])
52
+ end
53
+ end
54
+
55
+ context "when cache is invalid" do
56
+ let(:valid?) { false }
57
+
58
+ it do
59
+ expect { cache.boards }
60
+ .to raise_error(RuntimeError, "This method should not be used since the cache is invalid")
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#save" do
66
+ def build_board(id)
67
+ instance_double(
68
+ Board,
69
+ jira_board: jira_resource_double(attrs: { "id" => id, "name" => "name for board ##{id}" })
70
+ )
71
+ end
72
+
73
+ let(:boards) { [4, 8].collect { |id| build_board(id) } }
74
+ let(:expected_board_yaml) do
75
+ <<~EOBYAML
76
+ ---
77
+ boards:
78
+ - id: 4
79
+ name: 'name for board #4'
80
+ - id: 8
81
+ name: 'name for board #8'
82
+ EOBYAML
83
+ end
84
+
85
+ before do
86
+ allow(cache).to receive_messages(file_path: "path/to/cache.yml")
87
+ end
88
+
89
+ it "returns the boards" do
90
+ expect(File).to receive(:write).with("path/to/cache.yml", expected_board_yaml)
91
+
92
+ expect(cache.save(boards)).to eq(boards)
93
+ end
94
+ end
95
+
96
+ describe "#clear" do
97
+ before do
98
+ allow(cache).to receive_messages(file_path: "path/to/cache.yml")
99
+ allow(FileUtils).to receive(:rm_f).with("path/to/cache.yml")
100
+ end
101
+
102
+ it "deletes the cache file" do
103
+ cache.clear
104
+
105
+ expect(FileUtils).to have_received(:rm_f).with("path/to/cache.yml")
106
+ end
107
+ end
108
+
109
+ describe "#valid?" do
110
+ before do
111
+ allow(cache).to receive_messages(file_path: "path/to/cache.yml")
112
+
113
+ allow(File).to receive(:exist?).with("path/to/cache.yml").and_return(exist?)
114
+ end
115
+
116
+ context "when cache file exists" do
117
+ let(:exist?) { true }
118
+
119
+ before do
120
+ allow(cache).to receive_messages(expired?: expired?)
121
+ end
122
+
123
+ context "when cache has not expired" do
124
+ let(:expired?) { false }
125
+
126
+ it { expect(cache).to be_valid }
127
+ end
128
+
129
+ context "when cache has expired" do
130
+ let(:expired?) { true }
131
+
132
+ it { expect(cache).not_to be_valid }
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#expired?" do
138
+ before do
139
+ allow(cache).to receive_messages(cached_at: cached_at)
140
+ end
141
+
142
+ context "when expired" do
143
+ let(:cached_at) { Time.now - 1.day }
144
+
145
+ it { expect(cache.send(:expired?)).to be_truthy }
146
+ end
147
+
148
+ context "when not expired" do
149
+ let(:cached_at) { Time.now }
150
+
151
+ it { expect(cache.send(:expired?)).to be_falsy }
152
+ end
153
+ end
154
+
155
+ describe "#one_hour_ago" do
156
+ let(:time_now) { Time.parse("2025-02-21 20:55:00 UTC") }
157
+
158
+ before { allow(Helpers::OverridableTime).to receive_messages(now: time_now) }
159
+
160
+ it { expect(cache.send(:one_day_ago)).to eq(Time.parse("2025-02-20 20:55:00 UTC")) }
161
+ end
162
+
163
+ describe "#file_path" do
164
+ let(:config) { instance_double(Config, dir: "path/to/config/dir") }
165
+
166
+ before do
167
+ allow(tool).to receive_messages(config: config)
168
+ end
169
+
170
+ it "is located in the config dir" do
171
+ expect(cache.send(:file_path)).to eq("path/to/config/dir/jira-auto-tool.cache.yml")
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/board/unavailable_board"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class Board
9
+ class UnavailableBoard < Board
10
+ RSpec.describe UnavailableBoard do
11
+ let(:tool) { instance_double(Tool) }
12
+ let(:board) { described_class.new(tool, 4) }
13
+
14
+ it { expect(board.id).to eq(4) }
15
+ it { expect(board.name).to eq("N/A") }
16
+ it { expect(board.url).to eq("N/A") }
17
+
18
+ describe "#<=>" do
19
+ def new_board(id)
20
+ described_class.new(tool, id)
21
+ end
22
+
23
+ let(:board_with_same_id) { new_board(4) }
24
+
25
+ it { expect(board <=> board_with_same_id).to eq(0) }
26
+ it { expect(new_board(1) <=> board).to eq(-1) }
27
+ it { expect(new_board(5) <=> board).to eq(1) }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ require "jira/auto/tool/board_controller/options"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class BoardController
11
+ class Options
12
+ # TODO: clean this autogenerated code
13
+ # rubocop:disable RSpec/MultipleExpectations
14
+ RSpec.describe Options do
15
+ let(:tool) { instance_double(Tool, board_controller: board_controller) }
16
+ let(:board_controller) { instance_double(BoardController) }
17
+ let(:parser) { instance_double(OptionParser) }
18
+
19
+ describe ".add" do
20
+ it "configures board cache clear option" do
21
+ expect(parser).to receive(:section_header).with("Board")
22
+ expect(parser).to receive(:on).with("--board-name=STRING", String)
23
+ expect(parser).to receive(:on).with("--board-cache-clear", any_args) do |&block|
24
+ expect(board_controller).to receive(:clear_cache)
25
+ block.call
26
+ end
27
+
28
+ expect(parser).to receive(:on).with("--board-list", any_args)
29
+
30
+ described_class.add(tool, parser)
31
+ end
32
+
33
+ it "configures board list option" do
34
+ expect(parser).to receive(:section_header).with("Board")
35
+ expect(parser).to receive(:on).with("--board-name=STRING", String)
36
+ expect(parser).to receive(:on).with("--board-cache-clear", any_args)
37
+
38
+ expect(parser).to receive(:on).with("--board-list", any_args) do |&block|
39
+ expect(board_controller).to receive(:list_boards)
40
+ block.call
41
+ end
42
+
43
+ described_class.add(tool, parser)
44
+ end
45
+ end
46
+ # rubocop:enable RSpec/MultipleExpectations
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end