files_spec.rb 12.3 KB
Newer Older
1 2
require 'spec_helper'

3
describe API::Files do
4
  let(:user) { create(:user) }
5 6
  let!(:project) { create(:project, :repository, namespace: user.namespace ) }
  let(:guest) { create(:user) { |u| project.add_guest(u) } }
7
  let(:file_path) { "files%2Fruby%2Fpopen%2Erb" }
8 9 10 11 12
  let(:params) do
    {
      ref: 'master'
    }
  end
13 14
  let(:author_email) { 'user@example.org' }
  let(:author_name) { 'John Doe' }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
15

16 17 18
  before do
    project.team << [user, :developer]
  end
19

20 21 22
  def route(file_path = nil)
    "/projects/#{project.id}/repository/files/#{file_path}"
  end
23

24
  describe "GET /projects/:id/repository/files/:file_path" do
25
    shared_examples_for 'repository files' do
26 27
      it 'returns file attributes as json' do
        get api(route(file_path), current_user), params
28 29

        expect(response).to have_http_status(200)
30
        expect(json_response['file_path']).to eq(CGI.unescape(file_path))
31 32 33 34
        expect(json_response['file_name']).to eq('popen.rb')
        expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
        expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
      end
35

36 37 38 39 40 41 42 43 44
      it 'returns json when file has txt extension' do
        file_path = "bar%2Fbranch-test.txt"

        get api(route(file_path), current_user), params

        expect(response).to have_http_status(200)
        expect(response.content_type).to eq('application/json')
      end

45 46 47 48 49 50 51 52 53 54 55 56
      it 'returns file by commit sha' do
        # This file is deleted on HEAD
        file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
        params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"

        get api(route(file_path), current_user), params

        expect(response).to have_http_status(200)
        expect(json_response['file_name']).to eq('commit.js.coffee')
        expect(Base64.decode64(json_response['content']).lines.first).to eq("class Commit\n")
      end

57 58 59 60 61 62 63 64 65 66
      it 'returns raw file info' do
        url = route(file_path) + "/raw"
        expect(Gitlab::Workhorse).to receive(:send_git_blob)

        get api(url, current_user), params

        expect(response).to have_http_status(200)
      end

      context 'when mandatory params are not given' do
67
        it_behaves_like '400 response' do
68
          let(:request) { get api(route("any%2Ffile"), current_user) }
69 70 71 72
        end
      end

      context 'when file_path does not exist' do
73
        let(:params) { { ref: 'master' } }
74 75

        it_behaves_like '404 response' do
76
          let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
77 78 79 80 81 82 83 84
          let(:message) { '404 File Not Found' }
        end
      end

      context 'when repository is disabled' do
        include_context 'disabled repository'

        it_behaves_like '403 response' do
85
          let(:request) { get api(route(file_path), current_user), params }
86 87
        end
      end
88 89
    end

90
    context 'when unauthenticated', 'and project is public' do
91
      it_behaves_like 'repository files' do
92
        let(:project) { create(:project, :public, :repository) }
93 94 95
        let(:current_user) { nil }
      end
    end
96

97 98
    context 'when unauthenticated', 'and project is private' do
      it_behaves_like '404 response' do
99
        let(:request) { get api(route(file_path)), params }
100
        let(:message) { '404 Project Not Found' }
101
      end
102 103
    end

104 105 106 107
    context 'when authenticated', 'as a developer' do
      it_behaves_like 'repository files' do
        let(:current_user) { user }
      end
108 109
    end

110 111
    context 'when authenticated', 'as a guest' do
      it_behaves_like '403 response' do
112
        let(:request) { get api(route(file_path), guest), params }
113
      end
114 115
    end
  end
116

117 118 119 120 121 122 123 124 125 126 127
  describe "GET /projects/:id/repository/files/:file_path/raw" do
    shared_examples_for 'repository raw files' do
      it 'returns raw file info' do
        url = route(file_path) + "/raw"
        expect(Gitlab::Workhorse).to receive(:send_git_blob)

        get api(url, current_user), params

        expect(response).to have_http_status(200)
      end

128 129 130 131 132 133 134 135 136
      it 'returns raw file info for files with dots' do
        url = route('.gitignore') + "/raw"
        expect(Gitlab::Workhorse).to receive(:send_git_blob)

        get api(url, current_user), params

        expect(response).to have_http_status(200)
      end

137 138 139 140 141 142 143 144 145 146 147
      it 'returns file by commit sha' do
        # This file is deleted on HEAD
        file_path = "files%2Fjs%2Fcommit%2Ejs%2Ecoffee"
        params[:ref] = "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9"
        expect(Gitlab::Workhorse).to receive(:send_git_blob)

        get api(route(file_path) + "/raw", current_user), params

        expect(response).to have_http_status(200)
      end

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
      context 'when mandatory params are not given' do
        it_behaves_like '400 response' do
          let(:request) { get api(route("any%2Ffile"), current_user) }
        end
      end

      context 'when file_path does not exist' do
        let(:params) { { ref: 'master' } }

        it_behaves_like '404 response' do
          let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb'), current_user), params }
          let(:message) { '404 File Not Found' }
        end
      end

      context 'when repository is disabled' do
        include_context 'disabled repository'

        it_behaves_like '403 response' do
          let(:request) { get api(route(file_path), current_user), params }
        end
      end
    end

    context 'when unauthenticated', 'and project is public' do
      it_behaves_like 'repository raw files' do
174
        let(:project) { create(:project, :public, :repository) }
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        let(:current_user) { nil }
      end
    end

    context 'when unauthenticated', 'and project is private' do
      it_behaves_like '404 response' do
        let(:request) { get api(route(file_path)), params }
        let(:message) { '404 Project Not Found' }
      end
    end

    context 'when authenticated', 'as a developer' do
      it_behaves_like 'repository raw files' do
        let(:current_user) { user }
      end
    end

    context 'when authenticated', 'as a guest' do
      it_behaves_like '403 response' do
        let(:request) { get api(route(file_path), guest), params }
      end
    end
  end

  describe "POST /projects/:id/repository/files/:file_path" do
    let!(:file_path) { "new_subfolder%2Fnewfile%2Erb" }
201
    let(:valid_params) do
202
      {
203 204 205
        branch: "master",
        content: "puts 8",
        commit_message: "Added newfile"
206
      }
207
    end
208

209
    it "creates a new file in project repo" do
210
      post api(route(file_path), user), valid_params
211

212
      expect(response).to have_http_status(201)
213
      expect(json_response["file_path"]).to eq(CGI.unescape(file_path))
214 215 216
      last_commit = project.repository.commit.raw
      expect(last_commit.author_email).to eq(user.email)
      expect(last_commit.author_name).to eq(user.name)
217 218
    end

219 220
    it "returns a 400 bad request if no mandatory params given" do
      post api(route("any%2Etxt"), user)
221

222
      expect(response).to have_http_status(400)
223 224
    end

225
    it "returns a 400 if editor fails to create file" do
226 227
      allow_any_instance_of(Repository).to receive(:create_file)
        .and_raise(Repository::CommitError, 'Cannot create file')
228

229
      post api(route("any%2Etxt"), user), valid_params
230

231
      expect(response).to have_http_status(400)
232
    end
233 234 235 236 237

    context "when specifying an author" do
      it "creates a new file with the specified author" do
        valid_params.merge!(author_email: author_email, author_name: author_name)

238
        post api(route("new_file_with_author%2Etxt"), user), valid_params
239 240

        expect(response).to have_http_status(201)
241
        expect(response.content_type).to eq('application/json')
242 243 244 245 246
        last_commit = project.repository.commit.raw
        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
247 248 249 250 251

    context 'when the repo is empty' do
      let!(:project) { create(:project_empty_repo, namespace: user.namespace ) }

      it "creates a new file in project repo" do
252
        post api(route("newfile%2Erb"), user), valid_params
253 254 255 256 257 258 259 260

        expect(response).to have_http_status(201)
        expect(json_response['file_path']).to eq('newfile.rb')
        last_commit = project.repository.commit.raw
        expect(last_commit.author_email).to eq(user.email)
        expect(last_commit.author_name).to eq(user.name)
      end
    end
261 262
  end

263
  describe "PUT /projects/:id/repository/files" do
264
    let(:valid_params) do
265
      {
266
        branch: 'master',
267 268 269
        content: 'puts 8',
        commit_message: 'Changed file'
      }
270
    end
271

272
    it "updates existing file in project repo" do
273
      put api(route(file_path), user), valid_params
274

275
      expect(response).to have_http_status(200)
276
      expect(json_response['file_path']).to eq(CGI.unescape(file_path))
277 278 279
      last_commit = project.repository.commit.raw
      expect(last_commit.author_email).to eq(user.email)
      expect(last_commit.author_name).to eq(user.name)
280 281
    end

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
    it "returns a 400 bad request if update existing file with stale last commit id" do
      params_with_stale_id = valid_params.merge(last_commit_id: 'stale')

      put api(route(file_path), user), params_with_stale_id

      expect(response).to have_http_status(400)
      expect(json_response['message']).to eq('You are attempting to update a file that has changed since you started editing it.')
    end

    it "updates existing file in project repo with accepts correct last commit id" do
      last_commit = Gitlab::Git::Commit
                        .last_for_path(project.repository, 'master', URI.unescape(file_path))
      params_with_correct_id = valid_params.merge(last_commit_id: last_commit.id)

      put api(route(file_path), user), params_with_correct_id

      expect(response).to have_http_status(200)
    end

301
    it "returns a 400 bad request if no params given" do
302
      put api(route(file_path), user)
303

304
      expect(response).to have_http_status(400)
305
    end
306 307 308 309 310

    context "when specifying an author" do
      it "updates a file with the specified author" do
        valid_params.merge!(author_email: author_email, author_name: author_name, content: "New content")

311
        put api(route(file_path), user), valid_params
312 313 314 315 316 317 318

        expect(response).to have_http_status(200)
        last_commit = project.repository.commit.raw
        expect(last_commit.author_email).to eq(author_email)
        expect(last_commit.author_name).to eq(author_name)
      end
    end
319
  end
320 321

  describe "DELETE /projects/:id/repository/files" do
322
    let(:valid_params) do
323
      {
324
        branch: 'master',
325 326
        commit_message: 'Changed file'
      }
327
    end
328

329
    it "deletes existing file in project repo" do
330
      delete api(route(file_path), user), valid_params
331

332
      expect(response).to have_http_status(204)
333 334
    end

335
    it "returns a 400 bad request if no params given" do
336
      delete api(route(file_path), user)
337

338
      expect(response).to have_http_status(400)
339 340
    end

341 342
    it "returns a 400 if fails to delete file" do
      allow_any_instance_of(Repository).to receive(:delete_file).and_raise(Repository::CommitError, 'Cannot delete file')
343

344
      delete api(route(file_path), user), valid_params
345

346
      expect(response).to have_http_status(400)
347
    end
348 349 350 351 352

    context "when specifying an author" do
      it "removes a file with the specified author" do
        valid_params.merge!(author_email: author_email, author_name: author_name)

353
        delete api(route(file_path), user), valid_params
354

355
        expect(response).to have_http_status(204)
356 357
      end
    end
358
  end
359 360

  describe "POST /projects/:id/repository/files with binary file" do
361
    let(:file_path) { 'test%2Ebin' }
362 363
    let(:put_params) do
      {
364
        branch: 'master',
365 366 367 368 369 370 371
        content: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=',
        commit_message: 'Binary file with a \n should not be touched',
        encoding: 'base64'
      }
    end
    let(:get_params) do
      {
372
        ref: 'master'
373 374 375 376
      }
    end

    before do
377
      post api(route(file_path), user), put_params
378 379 380
    end

    it "remains unchanged" do
381
      get api(route(file_path), user), get_params
382

383
      expect(response).to have_http_status(200)
384 385
      expect(json_response['file_path']).to eq(CGI.unescape(file_path))
      expect(json_response['file_name']).to eq(CGI.unescape(file_path))
386 387 388
      expect(json_response['content']).to eq(put_params[:content])
    end
  end
389
end