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,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+
5
+ require "jira/auto/tool/ticket"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class Ticket
11
+ RSpec.describe Ticket do
12
+ let(:jira_client) { instance_double(JIRA::Client) }
13
+ let(:tool) { instance_double(Tool, jira_client: jira_client) }
14
+ let(:jira_ticket) { jira_resource_double(JIRA::Resource::Issue, client: jira_client, key: "ART-12345") }
15
+ let(:ticket) { described_class.new(tool, jira_ticket, nil, nil) }
16
+ let(:sprint_field) { instance_double(Field, id: "customfield_12345", name: "Sprint") }
17
+
18
+ describe "#key" do
19
+ it { expect(ticket.key).to eq("ART-12345") }
20
+ end
21
+
22
+ context "when accessing Jira field information" do
23
+ describe "#jira_sprint_field" do
24
+ before { allow(tool).to receive_messages(jira_sprint_field: sprint_field) }
25
+
26
+ it { expect(ticket.jira_sprint_field).to eq(sprint_field) }
27
+ end
28
+
29
+ describe "#expected_start_date_field" do
30
+ let(:expected_start_date_field) do
31
+ instance_double(Field, id: "customfield_80044", name: "Expected Start")
32
+ end
33
+
34
+ before { allow(tool).to receive_messages(expected_start_date_field: expected_start_date_field) }
35
+
36
+ it { expect(ticket.expected_start_date_field).to eq(expected_start_date_field) }
37
+ end
38
+ end
39
+
40
+ context "when accessing implementation_team information" do
41
+ let(:implementation_team_field) do
42
+ instance_double(Field, id: "customfield_80044", name: "Implementation Team")
43
+ end
44
+
45
+ before { allow(tool).to receive_messages(implementation_team_field: implementation_team_field) }
46
+
47
+ describe "#implementation_team_field" do
48
+ it { expect(ticket.implementation_team_field).to eq(implementation_team_field) }
49
+ end
50
+
51
+ describe "#implementation_team" do
52
+ before do
53
+ allow(jira_ticket).to receive_messages(fields: fields)
54
+ allow(ticket).to receive_messages(implementation_team_field: implementation_team_field)
55
+ end
56
+
57
+ context "when not value specified" do
58
+ let(:fields) { { "customfield_80044" => nil } }
59
+
60
+ it { expect(ticket.implementation_team).to be_nil }
61
+ end
62
+
63
+ context "when stored as value" do
64
+ let(:fields) { { "customfield_80044" => { "value" => "A16 Logistic" } } }
65
+
66
+ it { expect(ticket.implementation_team).to eq("A16 Logistic") }
67
+ end
68
+
69
+ context "when stored as name" do
70
+ let(:fields) { { "customfield_80044" => { "name" => "A16 Logistic" } } }
71
+
72
+ it { expect(ticket.implementation_team).to eq("A16 Logistic") }
73
+ end
74
+
75
+ context "when the field is not present" do
76
+ let(:fields) { { "another key" => "some other value" } }
77
+
78
+ it "reports missing information" do
79
+ expect { ticket.implementation_team }
80
+ .to raise_error(RuntimeError,
81
+ /customfield_80044: value not found in #{fields.inspect}/)
82
+ end
83
+ end
84
+
85
+ context "when value or name are not present" do
86
+ let(:fields) { { "customfield_80044" => field_attributes } }
87
+ let(:field_attributes) { { "key" => "some value", "another key" => "some other value" } }
88
+
89
+ it "reports missing information" do
90
+ expect { ticket.implementation_team }
91
+ .to raise_error(
92
+ RuntimeError,
93
+ /Implementation team value and name attributes not found in #{field_attributes.inspect}!/
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "#sprint=" do
101
+ let(:sprint) { instance_double(Sprint, name: "a sprint", id: 50_400) }
102
+
103
+ it "updates the Jira ticket" do
104
+ allow(tool).to receive_messages(jira_sprint_field: sprint_field)
105
+ allow(jira_ticket).to receive_messages(save!: nil)
106
+
107
+ ticket.sprint = sprint
108
+
109
+ expect(jira_ticket).to have_received(:save!).with({ "fields" => { "customfield_12345" => 50_400 } })
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "active_support/testing/time_helpers"
5
+ require "jira/auto/tool/until_date"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ RSpec.describe Jira::Auto::Tool::UntilDate do
11
+ it "accepts a date with time" do
12
+ expect(described_class.new("2024-12-19 13:16 UTC").time).to eq(Time.new(2024, 12, 19, 13, 16, 0, "+00:00"))
13
+ end
14
+
15
+ RSpec::Matchers.define :be_date_until_midnight_utc do |expected_year, expected_month, expected_day|
16
+ def date_until_midnight_utc(expected_year, expected_month, expected_day)
17
+ Time.new(expected_year, expected_month, expected_day, 23, 59, 59, "UTC").end_of_day
18
+ end
19
+
20
+ match do |actual_until_date|
21
+ @date_until_midnight_utc = date_until_midnight_utc(expected_year, expected_month, expected_day)
22
+
23
+ expect(actual_until_date.time).to eq(@date_until_midnight_utc)
24
+ end
25
+
26
+ failure_message { |actual_until_date| build_message(actual_until_date) }
27
+ failure_message_when_negated { |actual_until_date| build_message(actual_until_date, "not ") }
28
+
29
+ def build_message(actual_until_date, negation_part = "")
30
+ "expected #{actual_until_date.inspect} #{negation_part}to be equal to #{@date_until_midnight_utc.inspect}"
31
+ end
32
+ end
33
+
34
+ context "when no time specified it adds the time until midnight UTC" do
35
+ it do
36
+ expect(described_class.new("2025-01-01")).to be_date_until_midnight_utc(2025, 1, 1)
37
+ end
38
+ end
39
+
40
+ context "when using named dates it converts to the time until midnight UTC" do
41
+ include ActiveSupport::Testing::TimeHelpers
42
+
43
+ let(:third_quarter_time) { Time.new(2024, 9, 16, 12, 0, 0, "+00:00") }
44
+
45
+ before { travel_to(third_quarter_time) }
46
+ after { travel_back }
47
+
48
+ it do
49
+ expect(described_class.new("today")).to be_date_until_midnight_utc(2024, 9, 16)
50
+ end
51
+
52
+ it do
53
+ expect(described_class.new("current_quarter")).to be_date_until_midnight_utc(2024, 9, 30)
54
+ end
55
+
56
+ it do
57
+ expect(described_class.new("coming_quarter")).to be_date_until_midnight_utc(2024, 12, 31)
58
+ end
59
+ end
60
+
61
+ context "when unexpected date format" do
62
+ it "raises an error" do
63
+ expect { described_class.new("current-quarter-end") }
64
+ .to raise_error(UntilDate::FormatError, "date string 'current-quarter-end' is not in a supported format")
65
+ end
66
+ end
67
+
68
+ describe "#current_date_time" do
69
+ let(:time_now) { Time.new(2024, 9, 16, 12, 0, 0, "+00:00") }
70
+
71
+ it "uses an overridable time source" do
72
+ allow(Helpers::OverridableTime).to receive_messages(now: time_now)
73
+
74
+ expect(described_class.new("current_quarter").current_date_time).to eq(time_now)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,458 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool"
4
+
5
+ module Jira
6
+ module Auto
7
+ # rubocop:disable Metrics/ClassLength
8
+ class Tool
9
+ RSpec.describe Tool do
10
+ let(:tool) { described_class.new }
11
+
12
+ it "has a version number" do
13
+ expect(Jira::Auto::Tool::VERSION).not_to be_nil
14
+ end
15
+
16
+ describe "#board_controller" do
17
+ before { allow(tool).to receive_messages(jira_client: instance_double(RateLimitedJiraClient)) }
18
+
19
+ it { expect(tool.board_controller).to be_a(BoardController) }
20
+ end
21
+
22
+ describe "#board" do
23
+ def board_double(name)
24
+ double("Board", name: name) # rubocop:disable RSpec/VerifiedDoubles
25
+ end
26
+
27
+ let(:expected_board) { board_double("a board name") }
28
+
29
+ let(:boards) do
30
+ other_boards = 0.upto(10).collect { |i| board_double("board_#{i}") }
31
+ other_boards << expected_board
32
+ other_boards.shuffle
33
+ end
34
+
35
+ it "has a board" do
36
+ allow(tool).to receive_messages(board_name: "a board name")
37
+ allow(tool).to receive_messages(boards: boards)
38
+
39
+ expect(tool.board).to equal(expected_board)
40
+ end
41
+ end
42
+
43
+ # rubocop:disable RSpec/StubbedMock
44
+ describe "#create_sprint" do
45
+ it "creates a future sprint and transitions it to the desired state" do
46
+ expect(tool).to receive(:transition_sprint_state).with(:created_sprint, desired_state: "a state")
47
+
48
+ expect(tool).to receive(:create_future_sprint)
49
+ .with({ name: "sprint_name_24.4.2", start_date: "2024-12-16 11:00 UTC",
50
+ length_in_days: 14 })
51
+ .and_return(:created_sprint)
52
+
53
+ tool.create_sprint({ name: "sprint_name_24.4.2", start_date: "2024-12-16 11:00 UTC", length_in_days: 14,
54
+ state: "a state" })
55
+ end
56
+
57
+ context "when specifying end_date" do
58
+ it "overrides the length_in_days" do
59
+ expect(tool).to receive(:transition_sprint_state).with(:created_sprint, desired_state: "a state")
60
+
61
+ expect(tool).to receive(:create_future_sprint)
62
+ .with({ name: "sprint_name_24.4.2", start_date: "2024-12-16 11:00 UTC",
63
+ end_date: "2024-12-23 11:00 UTC" })
64
+ .and_return(:created_sprint)
65
+
66
+ tool.create_sprint({ name: "sprint_name_24.4.2", start_date: "2024-12-16 11:00 UTC",
67
+ end_date: "2024-12-23 11:00 UTC",
68
+ state: "a state" })
69
+ end
70
+ end
71
+ end
72
+ # rubocop:enable RSpec/StubbedMock
73
+
74
+ describe "#fetch_sprint" do
75
+ def build_sprint(name)
76
+ instance_double(Sprint, name: name)
77
+ end
78
+
79
+ let(:expected_sprint) { build_sprint("expected_sprint_name_24.4.1") }
80
+
81
+ let(:board_sprints) do
82
+ other_boards = 4.times.to_a.collect { |i| build_sprint("sprint_#{i}") }
83
+ other_boards << expected_sprint
84
+ other_boards.shuffle
85
+ end
86
+
87
+ let(:actual_sprint_controller) { instance_double(SprintController, sprints: board_sprints) }
88
+
89
+ it do
90
+ allow(tool).to receive_messages(sprint_controller: actual_sprint_controller)
91
+
92
+ expect(tool.fetch_sprint("expected_sprint_name_24.4.1")).to eq(expected_sprint)
93
+ end
94
+ end
95
+
96
+ describe "#sprint_controller" do
97
+ let(:board) { instance_double(Board, project_key: "board_project_key") }
98
+
99
+ before do
100
+ allow(tool).to receive_messages(board: board)
101
+ end
102
+
103
+ it { expect(tool.sprint_controller).to be_a(SprintController) }
104
+ end
105
+
106
+ describe "#project" do
107
+ let(:jira_client) { instance_double(RateLimitedJiraClient, Project: project_query) }
108
+ let(:jira_project) { instance_double(JIRA::Resource::Project) }
109
+ let(:jira_project_key) { "JIRA_PROJECT_KEY" }
110
+ let(:project_query) { double("project_query", find: jira_project) } # rubocop:disable RSpec/VerifiedDoubles
111
+
112
+ before do
113
+ allow(tool).to receive_messages(jira_project_key: jira_project_key, jira_client: jira_client)
114
+ end
115
+
116
+ it { expect(tool.project).to be_a(Project) }
117
+ end
118
+
119
+ # TODO: move that to environment_based_value_spec
120
+ RSpec.shared_examples "an overridable environment based value" do |method_name|
121
+ let(:env_var_name) { method_name.to_s.upcase }
122
+ let(:method_name?) { :"#{method_name}_defined?" }
123
+ let(:config) { Config.new(object_with_overridable_value) }
124
+
125
+ before do
126
+ allow(object_with_overridable_value).to receive_messages(config: config)
127
+ allow(config).to receive_messages(value_store: {})
128
+ end
129
+
130
+ context "when the environment variable is set" do
131
+ let(:expected_value) { "#{env_var_name} env_value" }
132
+
133
+ before do
134
+ allow(ENV).to receive(:fetch).with(env_var_name).and_return(expected_value)
135
+ allow(ENV).to receive(:key?).with(env_var_name).and_return(true)
136
+ end
137
+
138
+ it("method_name?") { expect(object_with_overridable_value.send(method_name?)).to be(true) }
139
+
140
+ it("method_name_when_defined_else") do
141
+ expect(object_with_overridable_value.send("#{method_name}_when_defined_else", "DEFAULT_VALUE"))
142
+ .to eq(expected_value)
143
+ end
144
+
145
+ it "fetches its value from the environment" do
146
+ expect(object_with_overridable_value.send(method_name)).to eq(expected_value)
147
+ end
148
+
149
+ # TODO: do the following also in case the env var is not defined
150
+ context "when the value is defined in the configuration" do
151
+ let(:expected_config_value) { "<<<<#{method_name} config value>>>>" }
152
+
153
+ before { allow(config).to receive_messages(value_store: { method_name.to_s => expected_config_value }) }
154
+
155
+ it "uses the configured value" do
156
+ expect(object_with_overridable_value.send("#{method_name}_when_defined_else", "DEFAULT_VALUE"))
157
+ .to eq(expected_config_value)
158
+ end
159
+ end
160
+ end
161
+
162
+ context "when the environment variable is not set" do
163
+ before do
164
+ allow(ENV).to receive(:fetch)
165
+ .with(env_var_name)
166
+ .and_raise(KeyError.new("Missing #{env_var_name} environment variable!)"))
167
+
168
+ allow(ENV).to receive(:key?).with(env_var_name).and_return(false)
169
+ end
170
+
171
+ it("method_name?") { expect(object_with_overridable_value.send(method_name?)).to be(false) }
172
+
173
+ it("method_name_when_defined_else") do
174
+ expect(object_with_overridable_value.send("#{method_name}_when_defined_else", "DEFAULT_VALUE"))
175
+ .to eq("DEFAULT_VALUE")
176
+ end
177
+
178
+ it "raises an error if the environment variable is not found" do
179
+ expect { object_with_overridable_value.send(method_name) }
180
+ .to raise_error(KeyError, /Missing #{env_var_name} environment variable!/)
181
+ end
182
+ end
183
+
184
+ it "can be overridden explicitly and updates the config" do
185
+ override_value = "override value for #{method_name}"
186
+
187
+ config = instance_double(Config)
188
+ allow(config).to receive_messages(:[]= => nil, :key? => true, :[] => override_value)
189
+ allow(object_with_overridable_value).to receive_messages(config: config)
190
+
191
+ object_with_overridable_value.send("#{method_name}=", override_value)
192
+
193
+ expect(object_with_overridable_value.send(method_name)).to eq(override_value)
194
+ expect(config).to have_received(:[]=).with(method_name, override_value)
195
+ end
196
+
197
+ it "defines an Environment constant with the same name" do
198
+ const_name = method_name.to_s.upcase
199
+ fully_qualified_const_name = "#{described_class}::Environment::#{const_name}"
200
+
201
+ expect(described_class.const_defined?(fully_qualified_const_name)).to be true
202
+ expect(described_class.const_get(fully_qualified_const_name)).to eq(const_name)
203
+ end
204
+ end
205
+
206
+ %i[
207
+ expected_start_date_field_name
208
+ implementation_team_field_name
209
+ jat_tickets_for_team_sprint_ticket_dispatcher_jql
210
+ jat_rate_limit_in_seconds
211
+ jat_rate_interval_in_seconds
212
+ jira_api_token
213
+ jira_board_name
214
+ jira_board_name_regex
215
+ jira_context_path
216
+ jira_http_debug
217
+ jira_project_key
218
+ jira_site_url jira_username
219
+ jira_sprint_field_name
220
+ ].each do |method_name|
221
+ describe "environment based values" do
222
+ let(:object_with_overridable_value) { tool }
223
+
224
+ it_behaves_like "an overridable environment based value", method_name
225
+ end
226
+ end
227
+
228
+ context "when dealing with jira site and context path related values" do
229
+ let(:jira_client) do
230
+ JIRA::Client.new({ site: "https://jira_site_url_value", context_path: "/context_path_value" })
231
+ end
232
+
233
+ before do
234
+ allow(tool).to receive_messages(jira_client: jira_client)
235
+ end
236
+
237
+ describe "#jira_base_url" do
238
+ it { expect(tool.jira_base_url).to eq("https://jira_site_url_value/context_path_value") }
239
+ end
240
+
241
+ describe "#jira_request_path" do
242
+ it { expect(tool.jira_request_path("/some/path")).to eq("/context_path_value/some/path") }
243
+ end
244
+
245
+ describe "#jira_url" do
246
+ before { allow(tool).to receive_messages(jira_base_url: "https://jira_site_url_value/context_path_value") }
247
+
248
+ it "has a jira instance url" do
249
+ expect(tool.jira_url("/board/4")).to eq("https://jira_site_url_value/context_path_value/board/4")
250
+ end
251
+ end
252
+ end
253
+
254
+ describe "#jira_client" do
255
+ let(:client_options) do
256
+ {
257
+ username: "jira_username_value",
258
+ password: "jira_api_token_value",
259
+ site: "https://jira_site_url_value",
260
+ context_path: "/context_path_value",
261
+ auth_type: :basic,
262
+ http_debug: false
263
+ }
264
+ end
265
+
266
+ before do
267
+ allow(tool)
268
+ .to receive_messages(jira_username: "jira_username_value", jira_site_url: "https://jira_site_url_value",
269
+ jira_api_token: "jira_api_token_value",
270
+ jira_context_path_when_defined_else: "/context_path_value",
271
+ jira_http_debug?: false,
272
+ jat_rate_limit_in_seconds_when_defined_else: "10",
273
+ jat_rate_interval_in_seconds_when_defined_else: "60")
274
+ end
275
+
276
+ it "has a jira client" do
277
+ expected_jira_client = instance_double(RateLimitedJiraClient)
278
+
279
+ allow(RateLimitedJiraClient)
280
+ .to receive(:new).with(client_options, rate_limit: 10, rate_interval: 60)
281
+ .and_return(expected_jira_client)
282
+
283
+ expect(tool.jira_client).to equal(expected_jira_client)
284
+ end
285
+ end
286
+
287
+ # TODO: overly complex - simplify
288
+ describe "#jira_http_debug?" do
289
+ let(:jira_http_debug_defined?) { true }
290
+ let(:config) { Config.new(tool) }
291
+
292
+ before do
293
+ allow(tool).to receive_messages(config: config)
294
+ end
295
+
296
+ context "when jira_http_debug is overridden with a specific value" do
297
+ it "can be overridden explicitly and updates the config" do
298
+ tool.jira_http_debug = true
299
+
300
+ expect(tool).to be_jira_http_debug
301
+ end
302
+
303
+ it "can be set to false" do
304
+ tool.jira_http_debug = false
305
+
306
+ expect(tool).not_to be_jira_http_debug
307
+ end
308
+ end
309
+
310
+ context "when jira_http_debug has not been overridden with a specific value" do
311
+ before do
312
+ allow(tool).to receive_messages(jira_http_debug: jira_http_debug,
313
+ jira_http_debug_defined?: jira_http_debug_defined?)
314
+ allow(config).to receive_messages(value_store: {})
315
+ end
316
+
317
+ context "when a true value is set for jira_http_debug" do
318
+ let(:jira_http_debug) { "true" }
319
+
320
+ it { expect(tool).to be_jira_http_debug }
321
+ end
322
+
323
+ context "when jira_http_debug is set to nil" do
324
+ let(:jira_http_debug) { nil }
325
+
326
+ it { expect(tool).not_to be_jira_http_debug }
327
+ end
328
+
329
+ context "when a false value is set for jira_http_debug" do
330
+ let(:jira_http_debug) { "false" }
331
+
332
+ it { expect(tool).not_to be_jira_http_debug }
333
+ end
334
+ end
335
+ end
336
+
337
+ context "when dealing with ticket fields" do
338
+ let(:ticket_field) { instance_double(JIRA::Resource::Field) }
339
+
340
+ describe "#project_ticket_fields" do
341
+ let(:project) { instance_double(Project, ticket_fields: [ticket_field]) }
342
+
343
+ before do
344
+ allow(tool).to receive_messages(project: project)
345
+ end
346
+
347
+ it { expect(tool.project_ticket_fields).not_to be_empty }
348
+ end
349
+
350
+ describe "#expected_start_date_field" do
351
+ let(:field_controller) { instance_double(FieldController, expected_start_date_field: ticket_field) }
352
+
353
+ it do
354
+ allow(tool).to receive_messages(field_controller: field_controller)
355
+
356
+ expect(tool.expected_start_date_field("start_date_field")).not_to be_nil
357
+ end
358
+ end
359
+
360
+ describe "#implementation_team_field" do
361
+ let(:field_controller) { instance_double(FieldController, implementation_team_field: ticket_field) }
362
+
363
+ it do
364
+ allow(tool).to receive_messages(field_controller: field_controller)
365
+
366
+ expect(tool.implementation_team_field("team_field")).not_to be_nil
367
+ end
368
+ end
369
+ end
370
+
371
+ context "when dealing with sprints" do
372
+ let(:expected_sprint_prefixes) { [instance_double(Sprint::Prefix)] }
373
+
374
+ let(:sprint_controller) do
375
+ instance_double(SprintController,
376
+ unclosed_sprints: ["a sprint", "another sprint"],
377
+ unclosed_sprint_prefixes: expected_sprint_prefixes)
378
+ end
379
+
380
+ before do
381
+ allow(tool).to receive_messages(sprint_controller: sprint_controller)
382
+ end
383
+
384
+ describe "#unclosed_sprints" do
385
+ it do
386
+ expect(tool.unclosed_sprints).to eq(["a sprint", "another sprint"])
387
+ end
388
+ end
389
+
390
+ describe "#unclosed_sprint_prefixes" do
391
+ it "returns an array of sprint prefixes" do
392
+ expect(tool.unclosed_sprint_prefixes).to eq(expected_sprint_prefixes)
393
+ end
394
+ end
395
+ end
396
+
397
+ context "when dealing with mapping team tickets to sprints" do
398
+ describe "#team_sprint_ticket_dispatcher" do
399
+ before do
400
+ allow(tool).to receive_messages(jira_client: nil,
401
+ unclosed_sprint_prefixes: nil,
402
+ jat_tickets_for_team_sprint_ticket_dispatcher_jql: :jql_for_tickets)
403
+
404
+ allow(tool).to receive(:tickets).with(:jql_for_tickets)
405
+ .and_return([instance_double(JIRA::Resource::Issue)])
406
+ end
407
+
408
+ it { expect(tool.team_sprint_ticket_dispatcher).to be_a(TeamSprintTicketDispatcher) }
409
+ end
410
+
411
+ describe "#tickets" do
412
+ let(:query) { jira_resource_double("query") }
413
+ let(:jira_client) { instance_double(RateLimitedJiraClient, Issue: query) }
414
+
415
+ before do
416
+ allow(tool).to receive_messages(jira_client: jira_client)
417
+
418
+ allow(tool)
419
+ .to receive_messages(project: jira_resource_double(JIRA::Resource::Project, key: "project_key"))
420
+
421
+ allow(query).to receive(:jql).with(expected_jql).and_return([instance_double(JIRA::Resource::Issue)])
422
+ end
423
+
424
+ context "without arguments" do
425
+ let(:expected_jql) { "project = project_key" }
426
+
427
+ it { expect(tool.tickets).to all be_a(Ticket) }
428
+ end
429
+
430
+ context "with a jql" do
431
+ let(:expected_jql) { "a jql" }
432
+
433
+ it { expect(tool.tickets(expected_jql)).to all be_a(Ticket) }
434
+ end
435
+ end
436
+
437
+ describe "#teams" do
438
+ let(:team_field_options) do
439
+ ["a team", "another team", "a third team"].collect do |team_name|
440
+ instance_double(FieldOption, value: team_name)
441
+ end
442
+ end
443
+
444
+ let(:team_field) { instance_double(Field, values: team_field_options) }
445
+
446
+ it do
447
+ allow(tool).to receive_messages(implementation_team_field: team_field)
448
+
449
+ expect(tool.teams).to eq(["a team", "another team", "a third team"])
450
+ end
451
+ end
452
+ end
453
+ end
454
+ end
455
+
456
+ # rubocop:enable Metrics/ClassLength
457
+ end
458
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov"
4
+
5
+ DISABLE_COVERAGE = ENV["DISABLE_COVERAGE"] == "true"
6
+
7
+ SimpleCov.start do
8
+ unless DISABLE_COVERAGE
9
+ minimum_coverage 90
10
+ minimum_coverage_by_file 80
11
+ end
12
+ end
13
+
14
+ require "jira/auto/tool"
15
+
16
+ RSpec.configure do |config|
17
+ Encoding.default_external = Encoding::UTF_8
18
+ Encoding.default_internal = Encoding::UTF_8
19
+
20
+ # Enable flags like --only-failures and --next-failure
21
+ config.example_status_persistence_file_path = ".rspec_status"
22
+
23
+ # Disable RSpec exposing methods globally on `Module` and `main`
24
+ config.disable_monkey_patching!
25
+
26
+ config.expect_with :rspec do |expectations|
27
+ expectations.syntax = :expect
28
+ expectations.max_formatted_output_length = nil
29
+ end
30
+
31
+ config.mock_with :rspec do |mocks|
32
+ mocks.verify_partial_doubles = true
33
+ end
34
+
35
+ config.include(Module.new do
36
+ def jira_resource_double(*args)
37
+ # rubocop:disable RSpec/VerifiedDoubles
38
+ double(*args)
39
+ # rubocop:enable RSpec/VerifiedDoubles
40
+ end
41
+ end)
42
+ end