jira_service_spec.rb 12.6 KB
Newer Older
1 2
require 'spec_helper'

D
Douwe Maan 已提交
3
describe JiraService, models: true do
4
  include Gitlab::Routing
5

6
  describe "Associations" do
7 8
    it { is_expected.to belong_to :project }
    it { is_expected.to have_one :service_hook }
9 10
  end

11 12
  describe 'Validations' do
    context 'when service is active' do
13 14 15
      before do
        subject.active = true
      end
16

F
Felipe Artur 已提交
17 18 19
      it { is_expected.to validate_presence_of(:url) }
      it { is_expected.to validate_presence_of(:project_key) }
      it_behaves_like 'issue tracker service URL attribute', :url
20 21 22
    end

    context 'when service is inactive' do
23 24 25
      before do
        subject.active = false
      end
26

F
Felipe Artur 已提交
27
      it { is_expected.not_to validate_presence_of(:url) }
28
    end
J
Jarka Kadlecova 已提交
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

    context 'validating urls' do
      let(:service) do
        described_class.new(
          project: create(:empty_project),
          active: true,
          username: 'username',
          password: 'test',
          project_key: 'TEST',
          jira_issue_transition_id: 24,
          url: 'http://jira.test.com'
        )
      end

      it 'is valid when all fields have required values' do
        expect(service).to be_valid
      end

      it 'is not valid when url is not a valid url' do
        service.url = 'not valid'

        expect(service).not_to be_valid
      end

      it 'is not valid when api url is not a valid url' do
        service.api_url = 'not valid'

        expect(service).not_to be_valid
      end

      it 'is valid when api url is a valid url' do
        service.api_url = 'http://jira.test.com/api'

        expect(service).to be_valid
      end
    end
65 66
  end

67
  describe '.reference_pattern' do
68 69 70
    it_behaves_like 'allows project key on reference pattern'

    it 'does not allow # on the code' do
71 72
      expect(described_class.reference_pattern.match('#123')).to be_nil
      expect(described_class.reference_pattern.match('1#23#12')).to be_nil
73 74 75
    end
  end

76
  describe '#close_issue' do
J
Jarka Kadlecova 已提交
77
    let(:custom_base_url) { 'http://custom_url' }
D
Drew Blessing 已提交
78
    let(:user)    { create(:user) }
79
    let(:project) { create(:empty_project) }
D
Drew Blessing 已提交
80 81 82 83 84 85 86 87
    let(:merge_request) { create(:merge_request) }

    before do
      @jira_service = JiraService.new
      allow(@jira_service).to receive_messages(
        project_id: project.id,
        project: project,
        service_hook: true,
F
Felipe Artur 已提交
88
        url: 'http://jira.example.com',
D
Drew Blessing 已提交
89
        username: 'gitlab_jira_username',
90
        password: 'gitlab_jira_password',
91 92
        project_key: 'GitLabProject',
        jira_issue_transition_id: "custom-id"
D
Drew Blessing 已提交
93
      )
F
Felipe Artur 已提交
94

95 96 97 98 99 100 101 102 103 104
      # These stubs are needed to test JiraService#close_issue.
      # We close the issue then do another request to API to check if it got closed.
      # Here is stubbed the API return with a closed and an opened issues.
      open_issue   = JIRA::Resource::Issue.new(@jira_service.client, attrs: { "id" => "JIRA-123" })
      closed_issue = open_issue.dup
      allow(open_issue).to receive(:resolution).and_return(false)
      allow(closed_issue).to receive(:resolution).and_return(true)
      allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue)

      allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return("JIRA-123")
105
      allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
106

F
Felipe Artur 已提交
107 108
      @jira_service.save

109 110 111 112 113 114 115 116 117
      project_issues_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123'
      @transitions_url   = 'http://jira.example.com/rest/api/2/issue/JIRA-123/transitions'
      @comment_url       = 'http://jira.example.com/rest/api/2/issue/JIRA-123/comment'
      @remote_link_url   = 'http://jira.example.com/rest/api/2/issue/JIRA-123/remotelink'

      WebMock.stub_request(:get, project_issues_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
      WebMock.stub_request(:post, @transitions_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
      WebMock.stub_request(:post, @comment_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
      WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
D
Drew Blessing 已提交
118 119
    end

120
    it "calls JIRA API" do
121
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
F
Felipe Artur 已提交
122

D
Drew Blessing 已提交
123 124 125 126 127
      expect(WebMock).to have_requested(:post, @comment_url).with(
        body: /Issue solved with/
      ).once
    end

128 129 130
    # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
    # for more information
    it "creates Remote Link reference in JIRA for comment" do
131
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
132 133 134 135 136 137 138 139 140 141 142 143

      # Creates comment
      expect(WebMock).to have_requested(:post, @comment_url)

      # Creates Remote Link in JIRA issue fields
      expect(WebMock).to have_requested(:post, @remote_link_url).with(
        body: hash_including(
          GlobalID: "GitLab",
          object: {
            url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{merge_request.diff_head_sha}",
            title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
            icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
F
Felipe Artur 已提交
144
            status: { resolved: true }
145 146 147 148 149 150 151 152
          }
        )
      ).once
    end

    it "does not send comment or remote links to issues already closed" do
      allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true)

153
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
154 155 156 157 158

      expect(WebMock).not_to have_requested(:post, @comment_url)
      expect(WebMock).not_to have_requested(:post, @remote_link_url)
    end

159
    it "references the GitLab commit/merge request" do
J
Jarka Kadlecova 已提交
160
      stub_config_setting(base_url: custom_base_url)
161

162
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
J
Jarka Kadlecova 已提交
163

164
      expect(WebMock).to have_requested(:post, @comment_url).with(
J
Jarka Kadlecova 已提交
165
        body: /#{custom_base_url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
166 167 168 169 170 171 172
      ).once
    end

    it "references the GitLab commit/merge request (relative URL)" do
      stub_config_setting(relative_url_root: '/gitlab')
      stub_config_setting(url: Settings.send(:build_gitlab_url))

173 174 175
      allow(JiraService).to receive(:default_url_options) do
        { script_name: '/gitlab' }
      end
176

177
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
178 179 180 181 182 183

      expect(WebMock).to have_requested(:post, @comment_url).with(
        body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/
      ).once
    end

D
Drew Blessing 已提交
184
    it "calls the api with jira_issue_transition_id" do
185
      @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project))
F
Felipe Artur 已提交
186 187

      expect(WebMock).to have_requested(:post, @transitions_url).with(
188
        body: /custom-id/
D
Drew Blessing 已提交
189 190
      ).once
    end
191
  end
192

193 194 195
  describe '#test_settings' do
    let(:jira_service) do
      described_class.new(
J
Jarka Kadlecova 已提交
196
        project: create(:project),
197
        url: 'http://jira.example.com',
J
Jarka Kadlecova 已提交
198 199
        username: 'jira_username',
        password: 'jira_password',
200 201 202
        project_key: 'GitLabProject'
      )
    end
203

J
Jarka Kadlecova 已提交
204
    def test_settings(api_url)
205
      project_url = "http://#{api_url}/rest/api/2/project/GitLabProject"
J
Jarka Kadlecova 已提交
206

207
      WebMock.stub_request(:get, project_url).with(basic_auth: %w(jira_username jira_password))
208 209

      jira_service.test_settings
J
Jarka Kadlecova 已提交
210 211 212 213 214
    end

    it 'tries to get JIRA project with URL when API URL not set' do
      test_settings('jira.example.com')
    end
215

J
Jarka Kadlecova 已提交
216 217 218
    it 'tries to get JIRA project with API URL if set' do
      jira_service.update(api_url: 'http://jira.api.com')
      test_settings('jira.api.com')
219
    end
D
Drew Blessing 已提交
220 221 222
  end

  describe "Stored password invalidation" do
223
    let(:project) { create(:empty_project) }
D
Drew Blessing 已提交
224 225 226

    context "when a password was previously set" do
      before do
227
        @jira_service = JiraService.create!(
228
          project: project,
D
Drew Blessing 已提交
229
          properties: {
J
Jarka Kadlecova 已提交
230
            url: 'http://jira.example.com/web',
D
Drew Blessing 已提交
231 232 233 234 235 236
            username: 'mic',
            password: "password"
          }
        )
      end

J
Jarka Kadlecova 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249 250
      context 'when only web url present' do
        it 'reset password if url changed' do
          @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
          @jira_service.save

          expect(@jira_service.password).to be_nil
        end

        it 'reset password if url not changed but api url added' do
          @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
          @jira_service.save

          expect(@jira_service.password).to be_nil
        end
D
Drew Blessing 已提交
251 252
      end

J
Jarka Kadlecova 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
      context 'when both web and api url present' do
        before do
          @jira_service.api_url = 'http://jira.example.com/rest/api/2'
          @jira_service.password = 'password'

          @jira_service.save
        end
        it 'reset password if api url changed' do
          @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
          @jira_service.save

          expect(@jira_service.password).to be_nil
        end

        it 'does not reset password if url changed' do
          @jira_service.url = 'http://jira_edited.example.com/rweb'
          @jira_service.save

          expect(@jira_service.password).to eq("password")
        end

        it 'reset password if api url set to ""' do
          @jira_service.api_url = ''
          @jira_service.save

          expect(@jira_service.password).to be_nil
        end
      end

      it 'does not reset password if username changed' do
        @jira_service.username = 'some_name'
D
Drew Blessing 已提交
284
        @jira_service.save
J
Jarka Kadlecova 已提交
285 286

        expect(@jira_service.password).to eq('password')
D
Drew Blessing 已提交
287 288
      end

J
Jarka Kadlecova 已提交
289
      it 'does not reset password if new url is set together with password, even if it\'s the same password' do
F
Felipe Artur 已提交
290
        @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
D
Drew Blessing 已提交
291 292
        @jira_service.password = 'password'
        @jira_service.save
J
Jarka Kadlecova 已提交
293 294 295

        expect(@jira_service.password).to eq('password')
        expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
D
Drew Blessing 已提交
296 297
      end

J
Jarka Kadlecova 已提交
298
      it 'resets password if url changed, even if setter called multiple times' do
F
Felipe Artur 已提交
299 300
        @jira_service.url = 'http://jira1.example.com/rest/api/2'
        @jira_service.url = 'http://jira1.example.com/rest/api/2'
D
Drew Blessing 已提交
301 302 303 304 305
        @jira_service.save
        expect(@jira_service.password).to be_nil
      end
    end

J
Jarka Kadlecova 已提交
306
    context 'when no password was previously set' do
D
Drew Blessing 已提交
307 308
      before do
        @jira_service = JiraService.create(
309
          project: project,
D
Drew Blessing 已提交
310
          properties: {
F
Felipe Artur 已提交
311
            url: 'http://jira.example.com/rest/api/2',
D
Drew Blessing 已提交
312 313 314 315 316
            username: 'mic'
          }
        )
      end

J
Jarka Kadlecova 已提交
317
      it 'saves password if new url is set together with password' do
F
Felipe Artur 已提交
318
        @jira_service.url = 'http://jira_edited.example.com/rest/api/2'
D
Drew Blessing 已提交
319 320
        @jira_service.password = 'password'
        @jira_service.save
J
Jarka Kadlecova 已提交
321 322
        expect(@jira_service.password).to eq('password')
        expect(@jira_service.url).to eq('http://jira_edited.example.com/rest/api/2')
D
Drew Blessing 已提交
323 324 325 326
      end
    end
  end

327
  describe 'description and title' do
328
    let(:project) { create(:empty_project) }
329 330 331 332 333 334 335 336 337 338

    context 'when it is not set' do
      before do
        @service = project.create_jira_service(active: true)
      end

      after do
        @service.destroy!
      end

339
      it 'is initialized' do
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
        expect(@service.title).to eq('JIRA')
        expect(@service.description).to eq("Jira issue tracker")
      end
    end

    context 'when it is set' do
      before do
        properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' }
        @service = project.create_jira_service(active: true, properties: properties)
      end

      after do
        @service.destroy!
      end

355
      it "is correct" do
356 357 358 359 360 361 362
        expect(@service.title).to eq('Jira One')
        expect(@service.description).to eq('Jira One issue tracker')
      end
    end
  end

  describe 'project and issue urls' do
363
    let(:project) { create(:empty_project) }
364 365 366

    context 'when gitlab.yml was initialized' do
      before do
D
Drew Blessing 已提交
367
        settings = {
J
Jarka Kadlecova 已提交
368 369 370 371
          'jira' => {
            'title' => 'Jira',
            'url' => 'http://jira.sample/projects/project_a',
            'api_url' => 'http://jira.sample/api'
372 373
          }
        }
374
        allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
375 376 377 378 379 380 381
        @service = project.create_jira_service(active: true)
      end

      after do
        @service.destroy!
      end

382
      it 'is prepopulated with the settings' do
J
Jarka Kadlecova 已提交
383 384 385
        expect(@service.properties['title']).to eq('Jira')
        expect(@service.properties['url']).to eq('http://jira.sample/projects/project_a')
        expect(@service.properties['api_url']).to eq('http://jira.sample/api')
386 387 388 389
      end
    end
  end
end