PageRenderTime 37ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/spec/lib/gitlab/bitbucket_import/importer_spec.rb

https://gitlab.com/jamgregory/gitlab-ce
Ruby | 324 lines | 293 code | 30 blank | 1 comment | 2 complexity | 03975d6c647fdae3bd39168fe57228f6 MD5 | raw file
  1. require 'spec_helper'
  2. describe Gitlab::BitbucketImport::Importer do
  3. include ImportSpecHelper
  4. before do
  5. stub_omniauth_provider('bitbucket')
  6. stub_feature_flags(stricter_mr_branch_name: false)
  7. end
  8. let(:statuses) do
  9. [
  10. "open",
  11. "resolved",
  12. "on hold",
  13. "invalid",
  14. "duplicate",
  15. "wontfix",
  16. "closed" # undocumented status
  17. ]
  18. end
  19. let(:reporters) do
  20. [
  21. nil,
  22. { "username" => "reporter1" },
  23. nil,
  24. { "username" => "reporter2" },
  25. { "username" => "reporter1" },
  26. nil,
  27. { "username" => "reporter3" }
  28. ]
  29. end
  30. let(:sample_issues_statuses) do
  31. issues = []
  32. statuses.map.with_index do |status, index|
  33. issues << {
  34. id: index,
  35. state: status,
  36. title: "Issue #{index}",
  37. kind: 'bug',
  38. content: {
  39. raw: "Some content to issue #{index}",
  40. markup: "markdown",
  41. html: "Some content to issue #{index}"
  42. }
  43. }
  44. end
  45. reporters.map.with_index do |reporter, index|
  46. issues[index]['reporter'] = reporter
  47. end
  48. issues
  49. end
  50. let(:project_identifier) { 'namespace/repo' }
  51. let(:data) do
  52. {
  53. 'bb_session' => {
  54. 'bitbucket_token' => "123456",
  55. 'bitbucket_refresh_token' => "secret"
  56. }
  57. }
  58. end
  59. let(:project) do
  60. create(
  61. :project,
  62. :repository,
  63. import_source: project_identifier,
  64. import_url: "https://bitbucket.org/#{project_identifier}.git",
  65. import_data_attributes: { credentials: data }
  66. )
  67. end
  68. let(:importer) { described_class.new(project) }
  69. let(:gitlab_shell) { double }
  70. let(:issues_statuses_sample_data) do
  71. {
  72. count: sample_issues_statuses.count,
  73. values: sample_issues_statuses
  74. }
  75. end
  76. let(:sample) { RepoHelpers.sample_compare }
  77. before do
  78. allow(importer).to receive(:gitlab_shell) { gitlab_shell }
  79. end
  80. subject { described_class.new(project) }
  81. describe '#import_pull_requests' do
  82. let(:source_branch_sha) { sample.commits.last }
  83. let(:target_branch_sha) { sample.commits.first }
  84. let(:pull_request) do
  85. instance_double(
  86. Bitbucket::Representation::PullRequest,
  87. iid: 10,
  88. source_branch_sha: source_branch_sha,
  89. source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch,
  90. target_branch_sha: target_branch_sha,
  91. target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch,
  92. title: 'This is a title',
  93. description: 'This is a test pull request',
  94. state: 'merged',
  95. author: 'other',
  96. created_at: Time.now,
  97. updated_at: Time.now)
  98. end
  99. before do
  100. allow(subject).to receive(:import_wiki)
  101. allow(subject).to receive(:import_issues)
  102. # https://gitlab.com/gitlab-org/gitlab-test/compare/c1acaa58bbcbc3eafe538cb8274ba387047b69f8...5937ac0a7beb003549fc5fd26fc247ad
  103. @inline_note = instance_double(
  104. Bitbucket::Representation::PullRequestComment,
  105. iid: 2,
  106. file_path: '.gitmodules',
  107. old_pos: nil,
  108. new_pos: 4,
  109. note: 'Hello world',
  110. author: 'root',
  111. created_at: Time.now,
  112. updated_at: Time.now,
  113. inline?: true,
  114. has_parent?: false)
  115. @reply = instance_double(
  116. Bitbucket::Representation::PullRequestComment,
  117. iid: 3,
  118. file_path: '.gitmodules',
  119. note: 'Hello world',
  120. author: 'root',
  121. created_at: Time.now,
  122. updated_at: Time.now,
  123. inline?: true,
  124. has_parent?: true,
  125. parent_id: 2)
  126. comments = [@inline_note, @reply]
  127. allow(subject.client).to receive(:repo)
  128. allow(subject.client).to receive(:pull_requests).and_return([pull_request])
  129. allow(subject.client).to receive(:pull_request_comments).with(anything, pull_request.iid).and_return(comments)
  130. end
  131. it 'imports threaded discussions' do
  132. expect { subject.execute }.to change { MergeRequest.count }.by(1)
  133. merge_request = MergeRequest.first
  134. expect(merge_request.notes.count).to eq(2)
  135. expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
  136. notes = merge_request.notes.order(:id).to_a
  137. start_note = notes.first
  138. expect(start_note).to be_a(DiffNote)
  139. expect(start_note.note).to eq(@inline_note.note)
  140. reply_note = notes.last
  141. expect(reply_note).to be_a(DiffNote)
  142. expect(reply_note.note).to eq(@reply.note)
  143. end
  144. context 'when importing a pull request throws an exception' do
  145. before do
  146. allow(pull_request).to receive(:raw).and_return('hello world')
  147. allow(subject.client).to receive(:pull_request_comments).and_raise(HTTParty::Error)
  148. end
  149. it 'logs an error without the backtrace' do
  150. subject.execute
  151. expect(subject.errors.count).to eq(1)
  152. expect(subject.errors.first.keys).to match_array(%i(type iid errors))
  153. end
  154. end
  155. context "when branches' sha is not found in the repository" do
  156. let(:source_branch_sha) { 'a' * Commit::MIN_SHA_LENGTH }
  157. let(:target_branch_sha) { 'b' * Commit::MIN_SHA_LENGTH }
  158. it 'uses the pull request sha references' do
  159. expect { subject.execute }.to change { MergeRequest.count }.by(1)
  160. merge_request_diff = MergeRequest.first.merge_request_diff
  161. expect(merge_request_diff.head_commit_sha).to eq source_branch_sha
  162. expect(merge_request_diff.start_commit_sha).to eq target_branch_sha
  163. end
  164. end
  165. end
  166. context 'issues statuses' do
  167. before do
  168. # HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this
  169. Bitbucket::Representation::Issue.new({})
  170. stub_request(
  171. :get,
  172. "https://api.bitbucket.org/2.0/repositories/#{project_identifier}"
  173. ).to_return(status: 200,
  174. headers: { "Content-Type" => "application/json" },
  175. body: { has_issues: true, full_name: project_identifier }.to_json)
  176. stub_request(
  177. :get,
  178. "https://api.bitbucket.org/2.0/repositories/#{project_identifier}/issues?pagelen=50&sort=created_on"
  179. ).to_return(status: 200,
  180. headers: { "Content-Type" => "application/json" },
  181. body: issues_statuses_sample_data.to_json)
  182. stub_request(:get, "https://api.bitbucket.org/2.0/repositories/namespace/repo?pagelen=50&sort=created_on")
  183. .with(headers: { 'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization' => 'Bearer', 'User-Agent' => 'Faraday v0.9.2' })
  184. .to_return(status: 200, body: "", headers: {})
  185. sample_issues_statuses.each_with_index do |issue, index|
  186. stub_request(
  187. :get,
  188. "https://api.bitbucket.org/2.0/repositories/#{project_identifier}/issues/#{issue[:id]}/comments?pagelen=50&sort=created_on"
  189. ).to_return(
  190. status: 200,
  191. headers: { "Content-Type" => "application/json" },
  192. body: { author_info: { username: "username" }, utc_created_on: index }.to_json
  193. )
  194. end
  195. stub_request(
  196. :get,
  197. "https://api.bitbucket.org/2.0/repositories/#{project_identifier}/pullrequests?pagelen=50&sort=created_on&state=ALL"
  198. ).to_return(status: 200,
  199. headers: { "Content-Type" => "application/json" },
  200. body: {}.to_json)
  201. end
  202. context 'creating labels on project' do
  203. before do
  204. allow(importer).to receive(:import_wiki)
  205. end
  206. it 'creates labels as expected' do
  207. expect { importer.execute }.to change { Label.count }.from(0).to(Gitlab::BitbucketImport::Importer::LABELS.size)
  208. end
  209. it 'does not fail if label is already existing' do
  210. label = Gitlab::BitbucketImport::Importer::LABELS.first
  211. ::Labels::CreateService.new(label).execute(project: project)
  212. expect { importer.execute }.not_to raise_error
  213. end
  214. it 'does not create new labels' do
  215. Gitlab::BitbucketImport::Importer::LABELS.each do |label|
  216. create(:label, project: project, title: label[:title])
  217. end
  218. expect { importer.execute }.not_to change { Label.count }
  219. end
  220. it 'does not update existing ones' do
  221. label_title = Gitlab::BitbucketImport::Importer::LABELS.first[:title]
  222. existing_label = create(:label, project: project, title: label_title)
  223. # Reload label from database so we avoid timestamp comparison issues related to time precision when comparing
  224. # attributes later.
  225. existing_label.reload
  226. Timecop.freeze(Time.now + 1.minute) do
  227. importer.execute
  228. label_after_import = project.labels.find(existing_label.id)
  229. expect(label_after_import.attributes).to eq(existing_label.attributes)
  230. end
  231. end
  232. end
  233. it 'maps statuses to open or closed' do
  234. allow(importer).to receive(:import_wiki)
  235. importer.execute
  236. expect(project.issues.where(state: "closed").size).to eq(5)
  237. expect(project.issues.where(state: "opened").size).to eq(2)
  238. end
  239. describe 'wiki import' do
  240. it 'is skipped when the wiki exists' do
  241. expect(project.wiki).to receive(:repository_exists?) { true }
  242. expect(importer.gitlab_shell).not_to receive(:import_wiki_repository)
  243. importer.execute
  244. expect(importer.errors).to be_empty
  245. end
  246. it 'imports to the project disk_path' do
  247. expect(project.wiki).to receive(:repository_exists?) { false }
  248. expect(importer.gitlab_shell).to receive(:import_wiki_repository)
  249. importer.execute
  250. expect(importer.errors).to be_empty
  251. end
  252. end
  253. describe 'issue import' do
  254. it 'maps reporters to anonymous if bitbucket reporter is nil' do
  255. allow(importer).to receive(:import_wiki)
  256. importer.execute
  257. expect(project.issues.size).to eq(7)
  258. expect(project.issues.where("description LIKE ?", '%Anonymous%').size).to eq(3)
  259. expect(project.issues.where("description LIKE ?", '%reporter1%').size).to eq(2)
  260. expect(project.issues.where("description LIKE ?", '%reporter2%').size).to eq(1)
  261. expect(project.issues.where("description LIKE ?", '%reporter3%').size).to eq(1)
  262. expect(importer.errors).to be_empty
  263. end
  264. end
  265. end
  266. end