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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +291 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Guardfile +105 -0
- data/LICENSE.txt +21 -0
- data/README.md +159 -0
- data/Rakefile +20 -0
- data/bin/jira-auto-tool +57 -0
- data/bin/jira-auto-tool.bat +2 -0
- data/bin/setup +8 -0
- data/bin/setup-dev-win.bat +3 -0
- data/cucumber.yml +7 -0
- data/features/align_sprint_time_in_dates.feature +33 -0
- data/features/assign_tickets_to_team_sprints.feature +73 -0
- data/features/cache_boards.feature +24 -0
- data/features/control_http_request_rate_limit.feature +17 -0
- data/features/create_sprints_using_existing_ones_as_reference.feature +79 -0
- data/features/list_boards.feature +12 -0
- data/features/list_project_fields.feature +89 -0
- data/features/list_sprint_prefixes.feature +77 -0
- data/features/quarterly_add_sprints_using_existing_ones_as_a_reference.feature +71 -0
- data/features/quarterly_create_sprints_until_specific_date.feature +75 -0
- data/features/quarterly_rename_sprints.feature +179 -0
- data/features/rename_sprints.feature +203 -0
- data/features/self_documented_command_line.feature +15 -0
- data/features/sprint_filtering.feature +111 -0
- data/features/step_definitions/execution_context_steps.rb +33 -0
- data/features/step_definitions/jira_board_steps.rb +102 -0
- data/features/step_definitions/jira_ticket_steps.rb +63 -0
- data/features/support/10.setup_cucumber.rb +10 -0
- data/features/support/env.rb +25 -0
- data/features/support/hooks.rb +25 -0
- data/features/support/setup_rspec.rb +14 -0
- data/features/support/setup_simplecov.rb +5 -0
- data/features/update_sprint_end_date_and_shift_following_ones.feature +52 -0
- data/lib/jira/auto/tool/board/cache.rb +67 -0
- data/lib/jira/auto/tool/board/unavailable_board.rb +36 -0
- data/lib/jira/auto/tool/board.rb +105 -0
- data/lib/jira/auto/tool/board_controller/options.rb +32 -0
- data/lib/jira/auto/tool/board_controller.rb +88 -0
- data/lib/jira/auto/tool/common_options.rb +37 -0
- data/lib/jira/auto/tool/config/options.rb +19 -0
- data/lib/jira/auto/tool/config.rb +64 -0
- data/lib/jira/auto/tool/fetch_custom_field_options.rb +47 -0
- data/lib/jira/auto/tool/field.rb +59 -0
- data/lib/jira/auto/tool/field_controller.rb +50 -0
- data/lib/jira/auto/tool/field_option.rb +35 -0
- data/lib/jira/auto/tool/get_createmeta_for_project.rb +24 -0
- data/lib/jira/auto/tool/helpers/environment_based_value.rb +96 -0
- data/lib/jira/auto/tool/helpers/option_parser.rb +16 -0
- data/lib/jira/auto/tool/helpers/overridable_time.rb +18 -0
- data/lib/jira/auto/tool/helpers/pagination.rb +50 -0
- data/lib/jira/auto/tool/jira_http_options.rb +20 -0
- data/lib/jira/auto/tool/next_sprint_creator.rb +60 -0
- data/lib/jira/auto/tool/performer/options.rb +76 -0
- data/lib/jira/auto/tool/performer/planning_increment_sprint_creator.rb +42 -0
- data/lib/jira/auto/tool/performer/prefix_sprint_updater.rb +42 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator.rb +60 -0
- data/lib/jira/auto/tool/performer/quarterly_sprint_renamer.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_end_date_updater.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator.rb +19 -0
- data/lib/jira/auto/tool/performer/sprint_renamer/next_name_generator.rb +47 -0
- data/lib/jira/auto/tool/performer/sprint_renamer.rb +55 -0
- data/lib/jira/auto/tool/performer/sprint_time_in_dates_aligner.rb +52 -0
- data/lib/jira/auto/tool/project/options.rb +22 -0
- data/lib/jira/auto/tool/project/ticket_fields.rb +70 -0
- data/lib/jira/auto/tool/project.rb +40 -0
- data/lib/jira/auto/tool/rate_limited_jira_client.rb +50 -0
- data/lib/jira/auto/tool/request_builder/field_context_fetcher.rb +78 -0
- data/lib/jira/auto/tool/request_builder/field_option_fetcher.rb +54 -0
- data/lib/jira/auto/tool/request_builder/get.rb +29 -0
- data/lib/jira/auto/tool/request_builder/sprint_creator.rb +112 -0
- data/lib/jira/auto/tool/request_builder/sprint_state_updater.rb +60 -0
- data/lib/jira/auto/tool/request_builder.rb +89 -0
- data/lib/jira/auto/tool/setup_logging.rb +35 -0
- data/lib/jira/auto/tool/sprint/name.rb +105 -0
- data/lib/jira/auto/tool/sprint/prefix.rb +66 -0
- data/lib/jira/auto/tool/sprint.rb +183 -0
- data/lib/jira/auto/tool/sprint_controller/options.rb +61 -0
- data/lib/jira/auto/tool/sprint_controller.rb +152 -0
- data/lib/jira/auto/tool/sprint_state_controller.rb +58 -0
- data/lib/jira/auto/tool/team.rb +23 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper/options.rb +27 -0
- data/lib/jira/auto/tool/team_sprint_prefix_mapper.rb +62 -0
- data/lib/jira/auto/tool/team_sprint_ticket_dispatcher.rb +76 -0
- data/lib/jira/auto/tool/ticket.rb +110 -0
- data/lib/jira/auto/tool/until_date.rb +68 -0
- data/lib/jira/auto/tool/version.rb +9 -0
- data/lib/jira/auto/tool.rb +216 -0
- data/sig/jira/sprint/tool.rbs +8 -0
- data/spec/jira/auto/tool/board/cache_spec.rb +179 -0
- data/spec/jira/auto/tool/board/unavailable_board_spec.rb +34 -0
- data/spec/jira/auto/tool/board_controller/options_spec.rb +52 -0
- data/spec/jira/auto/tool/board_controller_spec.rb +154 -0
- data/spec/jira/auto/tool/board_spec.rb +163 -0
- data/spec/jira/auto/tool/common_options_spec.rb +49 -0
- data/spec/jira/auto/tool/config_spec.rb +108 -0
- data/spec/jira/auto/tool/field_controller_spec.rb +121 -0
- data/spec/jira/auto/tool/field_option_spec.rb +42 -0
- data/spec/jira/auto/tool/field_spec.rb +99 -0
- data/spec/jira/auto/tool/helpers/environment_based_value_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/option_parser_spec.rb +21 -0
- data/spec/jira/auto/tool/helpers/overridable_time_spec.rb +43 -0
- data/spec/jira/auto/tool/helpers/pagination_spec.rb +72 -0
- data/spec/jira/auto/tool/jira_http_options_spec.rb +32 -0
- data/spec/jira/auto/tool/next_sprint_creator_spec.rb +85 -0
- data/spec/jira/auto/tool/performer/option_spec.rb +55 -0
- data/spec/jira/auto/tool/performer/planning_increment_sprint_creator_spec.rb +62 -0
- data/spec/jira/auto/tool/performer/prefix_sprint_updater_spec.rb +35 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer/next_name_generator_spec.rb +175 -0
- data/spec/jira/auto/tool/performer/quarterly_sprint_renamer_spec.rb +239 -0
- data/spec/jira/auto/tool/performer/sprint_end_date_updater_spec.rb +90 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/keep_same_name_generator_spec.rb +12 -0
- data/spec/jira/auto/tool/performer/sprint_renamer/next_name_generator_spec.rb +129 -0
- data/spec/jira/auto/tool/performer/sprint_renamer_spec.rb +240 -0
- data/spec/jira/auto/tool/performer/sprint_time_in_dates_aligner_spec.rb +132 -0
- data/spec/jira/auto/tool/project/ticket_fields_spec.rb +390 -0
- data/spec/jira/auto/tool/project_spec.rb +31 -0
- data/spec/jira/auto/tool/rate_limited_jira_client_spec.rb +82 -0
- data/spec/jira/auto/tool/request_builder/field_context_fetcher_spec.rb +54 -0
- data/spec/jira/auto/tool/request_builder/field_option_fetcher_spec.rb +64 -0
- data/spec/jira/auto/tool/request_builder/get_spec.rb +40 -0
- data/spec/jira/auto/tool/request_builder/sprint_creator_spec.rb +179 -0
- data/spec/jira/auto/tool/request_builder/sprint_state_updater_spec.rb +31 -0
- data/spec/jira/auto/tool/request_builder_spec.rb +73 -0
- data/spec/jira/auto/tool/sprint/name_spec.rb +101 -0
- data/spec/jira/auto/tool/sprint/prefix_spec.rb +207 -0
- data/spec/jira/auto/tool/sprint_controller_spec.rb +406 -0
- data/spec/jira/auto/tool/sprint_spec.rb +309 -0
- data/spec/jira/auto/tool/team_spec.rb +21 -0
- data/spec/jira/auto/tool/team_sprint_prefix_mapper_spec.rb +97 -0
- data/spec/jira/auto/tool/team_sprint_ticket_dispatcher_spec.rb +232 -0
- data/spec/jira/auto/tool/ticket_spec.rb +116 -0
- data/spec/jira/auto/tool/until_date_spec.rb +80 -0
- data/spec/jira/auto/tool_spec.rb +458 -0
- data/spec/spec_helper.rb +42 -0
- 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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|