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,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/board_controller"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class BoardController
9
+ def self.filtered_boards
10
+ board_controller.send(:filtered_boards)
11
+ end
12
+
13
+ # rubocop:disable RSpec/MultipleMemoizedHelpers
14
+ RSpec.describe BoardController do
15
+ let(:jira_client) { instance_double(JIRA::Client) }
16
+ let(:jira_boards) do
17
+ board_info.collect do |name, project_key, url|
18
+ jira_resource_double(JIRA::Resource::Board, name:, project_key:, url:)
19
+ end
20
+ end
21
+
22
+ let(:expected_boards) do
23
+ board_info.collect do |name, project_key, url, with_project_information|
24
+ build_board(name, project_key, url, with_project_information: with_project_information)
25
+ end
26
+ end
27
+
28
+ let(:tool) { instance_double(Tool, jira_client: jira_client) }
29
+ let(:board_controller) { described_class.new(tool) }
30
+
31
+ let(:board_info) do
32
+ [
33
+ ["Board 1", "ART", "https://jira.example.com/projects/ART/boards/1", true],
34
+ ["Board 2", "ART", "https://jira.example.com/projects/ART/boards/2", true],
35
+ ["Board 3", "TOOL", "https://jira.example.com/projects/TOOL/boards/3", true],
36
+ ["Board 4", "N/A", "https://jira.example.com/projects/TOOL/boards/4", false]
37
+ ]
38
+ end
39
+
40
+ def build_board(name, project_key, ui_url, with_project_information: true)
41
+ instance_double(Board, name:, project_key:, ui_url:, with_project_information?: with_project_information,
42
+ inspect: name)
43
+ end
44
+
45
+ describe "#list_boards" do
46
+ let(:expected_board_list) do
47
+ <<~EOBL
48
+ +-------------------------------------------------------------------------+
49
+ | Boards |
50
+ +-------------+---------+-------------------------------------------------+
51
+ | Project Key | Name | Board UI URL |
52
+ +-------------+---------+-------------------------------------------------+
53
+ | ART | Board 1 | https://jira.example.com/projects/ART/boards/1 |
54
+ | ART | Board 2 | https://jira.example.com/projects/ART/boards/2 |
55
+ | TOOL | Board 3 | https://jira.example.com/projects/TOOL/boards/3 |
56
+ | N/A | Board 4 | https://jira.example.com/projects/TOOL/boards/4 |
57
+ +-------------+---------+-------------------------------------------------+
58
+ EOBL
59
+ end
60
+
61
+ before do
62
+ allow(board_controller).to receive_messages(boards: expected_boards)
63
+ end
64
+
65
+ it { expect { board_controller.list_boards }.to output(expected_board_list).to_stdout }
66
+ end
67
+
68
+ describe "#boards" do
69
+ context "when the cache is invalid" do
70
+ let(:boards) { %i[a_board another_board] }
71
+
72
+ it "uses the API to fetch the boards and stores them in the cache" do
73
+ allow(board_controller)
74
+ .to receive_messages(filtered_boards: boards, valid_cache?: false)
75
+
76
+ allow(board_controller).to receive_messages(cache_boards: boards)
77
+
78
+ expect(board_controller.boards).to eq(boards)
79
+ end
80
+ end
81
+
82
+ context "when the cache is valid" do
83
+ it "uses the API to fetch the boards and stores them in the cache" do
84
+ allow(board_controller)
85
+ .to receive_messages(filtered_boards: %i[a_board another_board], valid_cache?: true)
86
+
87
+ allow(board_controller).to receive_messages(cached_boards: %i[a_board another_board])
88
+
89
+ expect(board_controller.boards).to eq(%i[a_board another_board])
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#filtered_boards" do
95
+ let(:project_key) { nil }
96
+ let(:board_name_regex) { nil }
97
+ let(:filtered_boards) { board_controller.send(:filtered_boards) }
98
+
99
+ before do
100
+ allow(board_controller).to receive_messages(unfiltered_boards: expected_boards)
101
+
102
+ allow(tool).to receive_messages(
103
+ jira_board_name_regex: board_name_regex,
104
+ jira_board_name_regex_defined?: board_name_regex,
105
+ jira_project_key: project_key,
106
+ jira_project_key_defined?: project_key
107
+ )
108
+ end
109
+
110
+ context "when using board name filtering" do
111
+ context "when a board name regex is specified" do
112
+ let(:board_name_regex) { "1|3" }
113
+
114
+ it { expect(filtered_boards).to eq(expected_boards.find_all { |b| b.name !~ /Board (2|4)/ }) }
115
+ end
116
+
117
+ context "when no board name regex is specified" do
118
+ it { expect(filtered_boards).to eq(expected_boards) }
119
+ end
120
+ end
121
+
122
+ context "when using project key filtering" do
123
+ context "when a project key is specified" do
124
+ let(:project_key) { "ART" }
125
+ let(:board_expected_to_be_excluded) do
126
+ build_board("Board 3", "ART", "https://jira.example.com/projects/ART/boards/3")
127
+ end
128
+
129
+ it { expect(filtered_boards).to eq(expected_boards.find_all { |b| b.name != "Board 3" }) }
130
+ it { expect(filtered_boards).not_to include(board_expected_to_be_excluded) }
131
+ end
132
+
133
+ context "when no project key is specified" do
134
+ it { expect(filtered_boards).to eq(expected_boards) }
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "#unfiltered_boards" do
140
+ let(:query) { jira_resource_double(all: jira_boards) }
141
+
142
+ before do
143
+ allow(jira_client).to receive_messages(Board: query)
144
+ end
145
+
146
+ it("returns boards") { expect(board_controller.send(:unfiltered_boards)).to all be_a(Board) }
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ # rubocop:enable RSpec/MultipleMemoizedHelpers
153
+ end
154
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/board"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class Board
9
+ RSpec.describe Board do
10
+ let(:jira_client) { instance_double(JIRA::Client) }
11
+ let(:tool) { instance_double(Tool, jira_client: jira_client) }
12
+
13
+ let(:jira_board) do
14
+ jira_resource_double(JIRA::Resource::Board, name: "board name", url: "BOARD_URL", id: 4)
15
+ end
16
+
17
+ let(:board) { described_class.new(tool, jira_board) }
18
+
19
+ it { expect(board.name).to eq("board name") }
20
+
21
+ it { expect(board.url).to eq("BOARD_URL") }
22
+
23
+ it { expect(board.id).to eq(4) }
24
+
25
+ describe "#<=>" do
26
+ def new_board(id)
27
+ described_class.new(tool, instance_double(JIRA::Resource::Board, id: id))
28
+ end
29
+
30
+ let(:board_with_same_id) { new_board(4) }
31
+
32
+ it { expect(board <=> board_with_same_id).to eq(0) }
33
+ it { expect(new_board(1) <=> board).to eq(-1) }
34
+ it { expect(new_board(5) <=> board).to eq(1) }
35
+ end
36
+
37
+ describe ".find_by_id" do
38
+ before do
39
+ allow(JIRA::Resource::Board).to receive(:find).with(jira_client, 4).and_return(jira_board)
40
+ end
41
+
42
+ it { expect(described_class.find_by_id(tool, 4)).to eq(board) }
43
+
44
+ it "deals with non available boards" do
45
+ allow(JIRA::Resource::Board)
46
+ .to receive(:find).with(jira_client, 16)
47
+ .and_raise(JIRA::HTTPError.new(instance_double(Net::HTTPResponse, code: "404")))
48
+
49
+ expect(described_class.find_by_id(tool, 16)).to eq(Board::UnavailableBoard.new(tool, 16))
50
+ end
51
+
52
+ it "caches the boards" do
53
+ allow(JIRA::Resource::Board).to receive(:find).with(jira_client, 4).and_return(jira_board).once
54
+
55
+ board_on_1st_call = described_class.find_by_id(tool, 4)
56
+
57
+ expect(described_class.find_by_id(tool, 4)).to be(board_on_1st_call)
58
+ end
59
+ end
60
+
61
+ describe "#unavailable?" do
62
+ it { expect(board).not_to be_unavailable }
63
+ it { expect(UnavailableBoard.new(tool, 512)).to be_unavailable }
64
+ end
65
+
66
+ describe ".to_table_row_field_names" do
67
+ it { expect(described_class.to_table_row_field_names).to eq(%i[name ui_url project_key]) }
68
+ end
69
+
70
+ describe ".to_table_row_header" do
71
+ it { expect(described_class.to_table_row_header).to eq(["Name", "UI URL", "Project Key"]) }
72
+ end
73
+
74
+ describe "#to_table_row" do
75
+ before do
76
+ allow(board).to receive_messages(project_key: "PROJECT_KEY", ui_url: "UI_URL")
77
+ end
78
+
79
+ it { expect(board.to_table_row).to eq(["board name", "UI_URL", "PROJECT_KEY"]) }
80
+ end
81
+
82
+ describe "#sprint_compatible?" do
83
+ context "when a scrum board" do
84
+ let(:jira_board) { jira_resource_double(JIRA::Resource::Board, type: "scrum") }
85
+
86
+ it { expect(board).to be_sprint_compatible }
87
+ end
88
+
89
+ context "when sprints are not available" do
90
+ let(:jira_board) { jira_resource_double(JIRA::Resource::Board, type: "non scrum") }
91
+
92
+ it { expect(board).not_to be_sprint_compatible }
93
+ end
94
+ end
95
+
96
+ describe "#ui_url" do
97
+ context "when location information is not available" do
98
+ before do
99
+ allow(board).to receive_messages(with_project_information?: false)
100
+
101
+ allow(tool).to receive(:jira_url)
102
+ .with("/secure/RapidBoard.jspa?rapidView=4")
103
+ .and_return("https://a-jira-site.example.com/jira/secure/RapidBoard.jspa?rapidView=4")
104
+ end
105
+
106
+ it do
107
+ expect(board.ui_url)
108
+ .to eq("https://a-jira-site.example.com/jira/secure/RapidBoard.jspa?rapidView=4")
109
+ end
110
+ end
111
+
112
+ context "when location information is available" do
113
+ # TODO: - fix the URL
114
+ before do
115
+ allow(board).to receive_messages(with_project_information?: true, project_key: "PROJECT_KEY")
116
+
117
+ allow(tool)
118
+ .to receive(:jira_url)
119
+ .with("/jira/software/c/projects/PROJECT_KEY/boards/4")
120
+ .and_return("https://a-jira-site.example.com/jira/software/c/projects/PROJECT_KEY/boards/4")
121
+ end
122
+
123
+ it do
124
+ expect(board.ui_url)
125
+ .to eq("https://a-jira-site.example.com/jira/software/c/projects/PROJECT_KEY/boards/4")
126
+ end
127
+ end
128
+ end
129
+
130
+ describe "#project_key" do
131
+ context "when location information is available" do
132
+ before do
133
+ allow(jira_board)
134
+ .to receive_messages(with_project_information?: true,
135
+ location: { "projectKey" => "PROJECT_KEY" })
136
+ end
137
+
138
+ it { expect(board.project_key).to eq("PROJECT_KEY") }
139
+ end
140
+
141
+ context "when location information is not available" do
142
+ it { expect(board.project_key).to eq("N/A") }
143
+ end
144
+ end
145
+
146
+ describe "#with_project_information?" do
147
+ context "when location information is available" do
148
+ before do
149
+ allow(jira_board).to receive_messages(location: :a_location)
150
+ end
151
+
152
+ it { expect(board).to be_with_project_information }
153
+ end
154
+
155
+ context "when location information is not available" do
156
+ it { expect(board).not_to be_with_project_information }
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/common_options"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ module CommonOptions
10
+ RSpec.describe CommonOptions do
11
+ describe ".add" do
12
+ let(:tool) { Tool.new }
13
+ let(:parser) { OptionParser.new }
14
+
15
+ before { described_class.add(tool, parser) }
16
+
17
+ context "when using --help" do
18
+ it do
19
+ expect(Kernel).to receive(:puts).with(parser)
20
+ expect(Kernel).to receive(:exit).with(1)
21
+
22
+ parser.parse(["--help"])
23
+ end
24
+ end
25
+
26
+ context "when using --version" do
27
+ it do
28
+ expect(Kernel).to receive(:puts).with(tool.class::VERSION)
29
+ expect(Kernel).to receive(:exit).with(1)
30
+
31
+ parser.parse(["--version"])
32
+ end
33
+ end
34
+
35
+ # TODO: define proper tests
36
+ def expect_option_use_to_be_valid(option_use_with_args)
37
+ expect do
38
+ parser.parse([option_use_with_args])
39
+ end.not_to raise_error
40
+ end
41
+
42
+ it { expect_option_use_to_be_valid(["--help"]) }
43
+ it { expect_option_use_to_be_valid(["-h"]) }
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "jira/auto/tool/config"
5
+
6
+ module Jira
7
+ module Auto
8
+ class Tool
9
+ class Config
10
+ RSpec.describe Config do
11
+ let(:config) { described_class.new(tool) }
12
+ let(:tool) { instance_double(Tool) }
13
+
14
+ describe "#[]" do
15
+ it "returns the value of the key" do
16
+ allow(config).to receive_messages(value_store: { "key" => "value" })
17
+
18
+ expect(config[:key]).to eq("value")
19
+ end
20
+ end
21
+
22
+ describe "#[]=" do
23
+ before do
24
+ allow(config).to receive_messages(value_store: { "one_key" => "a_value" }, save: nil)
25
+ end
26
+
27
+ it "updates the value and saves the configuration" do
28
+ config[:key] = "value"
29
+
30
+ expect(config[:key]).to eq("value")
31
+ expect(config).to have_received(:save)
32
+ end
33
+ end
34
+
35
+ describe "#key?" do
36
+ before do
37
+ allow(config).to receive_messages(value_store: { "one_key" => "a_value" }, save: nil)
38
+ end
39
+
40
+ it { expect(config).to be_key(:one_key) }
41
+ it { expect(config).not_to be_key(:inexistent_key) }
42
+ end
43
+
44
+ describe "#load" do
45
+ let(:config_path) { "config-path" }
46
+
47
+ before do
48
+ allow(config).to receive_messages(path: config_path)
49
+ allow(File).to receive(:exist?).with(config_path).and_return(config_path_exists?)
50
+ allow(YAML).to receive(:safe_load_file).with(config_path).and_return(:config_values)
51
+ end
52
+
53
+ context "when the file exists" do
54
+ let(:config_path_exists?) { true }
55
+
56
+ it "loads the configuration from the file" do
57
+ config.send(:load)
58
+
59
+ expect(config).to eq(:config_values)
60
+ end
61
+ end
62
+
63
+ context "when the file does not exists" do
64
+ let(:config_path_exists?) { false }
65
+
66
+ it { expect(config).to eq({}) }
67
+ end
68
+ end
69
+
70
+ describe "#save" do
71
+ it "writes the configuration as yaml to the file" do
72
+ allow(config).to receive_messages(path: "config.yml", value_store: { key: "value" })
73
+ allow(File).to receive_messages(write: nil)
74
+
75
+ config.send(:save)
76
+
77
+ expect(File).to have_received(:write).with("config.yml", "---\n:key: value\n")
78
+ end
79
+ end
80
+
81
+ describe "#path" do
82
+ before do
83
+ allow(config).to receive_messages(dir: "path/to/config_dir")
84
+ end
85
+
86
+ it { expect(config.send(:path)).to eq("path/to/config_dir/jira-auto-tool.config.yml") }
87
+ end
88
+
89
+ describe "#dir" do
90
+ let(:expected_dir) { "~/.config/jira-auto-tool" }
91
+
92
+ before do
93
+ allow(Dir).to receive_messages(home: "~/")
94
+ end
95
+
96
+ it { expect(config.send(:dir)).to eq(expected_dir) }
97
+
98
+ it "creates the directory if it does not exist" do
99
+ expect(FileUtils).to receive(:makedirs).with(expected_dir)
100
+
101
+ config.send(:dir)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ require "jira/auto/tool/field_controller"
6
+
7
+ module Jira
8
+ module Auto
9
+ class Tool
10
+ class FieldController
11
+ RSpec.describe FieldController do
12
+ let(:jira_client) { instance_double(JIRA::Client) }
13
+ let(:controller) { described_class.new(jira_client) }
14
+
15
+ def build_field(name = "a field name #{rand}", type = "unexpected_type")
16
+ instance_double(Field, name: name, type: type)
17
+ end
18
+
19
+ RSpec.shared_examples "a field fetcher" do
20
+ |method_under_test, expected_field_name, expected_field_type, actual_type = expected_field_type|
21
+
22
+ let(:expected_field) { build_field(expected_field_name, actual_type) }
23
+ let(:field_with_incorrect_type_name) { "non_#{expected_field_type}_field" }
24
+
25
+ before do
26
+ allow(controller)
27
+ .to receive(:ticket_fields)
28
+ .and_return([build_field, expected_field, build_field(field_with_incorrect_type_name), build_field])
29
+ end
30
+
31
+ context "when the field is found" do
32
+ let(:actual_field) { controller.send(method_under_test, expected_field_name) }
33
+
34
+ it "returns the expected field" do
35
+ expect(actual_field).to eq(expected_field)
36
+ end
37
+
38
+ it "has the expected type" do
39
+ expect(actual_field.type).to match_regex(expected_field_type)
40
+ end
41
+ end
42
+
43
+ it "report an unexpected field type" do
44
+ expect { controller.send(method_under_test, field_with_incorrect_type_name) }
45
+ .to raise_error(
46
+ ExpectedFieldTypeError,
47
+ /Field\ '#{field_with_incorrect_type_name}'\ expected\ to\ have\ type\ '#{expected_field_type}',
48
+ \ but\ was \ 'unexpected_type'./x
49
+ )
50
+ end
51
+
52
+ it "raises and error for a non existing field" do
53
+ expect { controller.send(method_under_test, "non_existing_field") }
54
+ .to raise_error(FieldNotFoundError, /Field 'non_existing_field' not found!/)
55
+ end
56
+ end
57
+
58
+ describe "#expected_start_date_field" do
59
+ it_behaves_like "a field fetcher", :expected_start_date_field, "Custom Start Date", "date"
60
+ end
61
+
62
+ describe "#implementation_team_field" do
63
+ context "when any field" do
64
+ it_behaves_like "a field fetcher", :implementation_team_field, "Custom Team", "option|any", "any"
65
+ end
66
+
67
+ context "when option field" do
68
+ it_behaves_like "a field fetcher", :implementation_team_field, "Custom Team", "option|any", "option"
69
+ end
70
+ end
71
+
72
+ describe "#sprint_field" do
73
+ it_behaves_like "a field fetcher", :sprint_field, "Custom Sprint", "array"
74
+ end
75
+
76
+ describe "#ticket_fields" do
77
+ let(:board_id) { 123 }
78
+ let(:jira_fields) do
79
+ [
80
+ %w[field1 date customfield_12345],
81
+ %w[field2 option customfield_12346]
82
+ ].collect { |attributes| build_jira_field(*attributes) }
83
+ end
84
+ let(:expected_fields) { jira_fields.collect { |f| Field.new(jira_client, f) } }
85
+
86
+ def build_jira_field(name, type, id)
87
+ jira_resource_double(JIRA::Resource::Field, name:, schema: { "type" => type }, id:)
88
+ end
89
+
90
+ before do
91
+ # rubocop:disable RSpec/MessageChain
92
+ allow(jira_client).to receive_message_chain(:Field, :all).and_return(jira_fields)
93
+ # rubocop:enable RSpec/MessageChain
94
+ end
95
+
96
+ context "when fields are successfully fetched" do
97
+ it "returns fields associated with the board" do
98
+ expect(controller.ticket_fields).to eq(expected_fields)
99
+ end
100
+ end
101
+
102
+ context "when an error occurs" do
103
+ before do
104
+ # rubocop:disable RSpec/MessageChain
105
+ allow(jira_client).to receive_message_chain(:Field, :all).and_raise(StandardError,
106
+ "Something went wrong")
107
+ # rubocop:enable RSpec/MessageChain
108
+ end
109
+
110
+ it "raises an error with a descriptive message" do
111
+ expect do
112
+ controller.ticket_fields
113
+ end.to raise_error(RuntimeError, /Error fetching project ticket fields: Something went wrong/)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jira/auto/tool/field_option"
4
+
5
+ module Jira
6
+ module Auto
7
+ class Tool
8
+ class FieldOption
9
+ RSpec.describe FieldOption do
10
+ let(:jira_client) { instance_double(JIRA::Client) }
11
+ let(:field_option) { described_class.new(jira_client, 123, "Test Option") }
12
+
13
+ describe "#initialize" do
14
+ it "assigns the id correctly" do
15
+ expect(field_option.id).to eq(123)
16
+ end
17
+
18
+ it "assigns the value correctly" do
19
+ expect(field_option.value).to eq("Test Option")
20
+ end
21
+ end
22
+
23
+ describe "#to_s" do
24
+ it "returns the correct string representation" do
25
+ expect(field_option.to_s).to eq("FieldOption(id: 123, value: 'Test Option')")
26
+ end
27
+ end
28
+
29
+ describe "#<=>" do
30
+ let(:higher_field_option) { described_class.new(jira_client, 124, "Another Option") }
31
+ let(:lower_field_option) { described_class.new(jira_client, 122, "Previous Option") }
32
+ let(:equal_field_option) { described_class.new(jira_client, 123, "Test Option") }
33
+
34
+ it { expect(field_option).to be < higher_field_option }
35
+ it { expect(field_option).to be > lower_field_option }
36
+ it { expect(field_option).to eq(equal_field_option) }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end