From 8735d95af16a6066e9f256a62f401d02c2c7e108 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Wed, 13 Jul 2016 23:05:57 +0800
Subject: [PATCH] Implement API for downloading artifacts from ref and build
 name:

Basically:

    GET /api/projects/:id/artifacts/:ref_name/:build_name

Also added tests for it.
---
 lib/api/api.rb                                |   1 +
 lib/api/artifacts.rb                          |  34 +++++
 spec/requests/api/artifacts_spec.rb           |  52 ++++++++
 .../projects/artifacts_controller_spec.rb     | 122 ++++--------------
 spec/requests/shared/artifacts_context.rb     |  78 +++++++++++
 5 files changed, 188 insertions(+), 99 deletions(-)
 create mode 100644 lib/api/artifacts.rb
 create mode 100644 spec/requests/api/artifacts_spec.rb
 create mode 100644 spec/requests/shared/artifacts_context.rb

diff --git a/lib/api/api.rb b/lib/api/api.rb
index 3d7d67510a8..f18258bf5a3 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -26,6 +26,7 @@ module API
     # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
     helpers ::API::Helpers
 
+    mount ::API::Artifacts
     mount ::API::AwardEmoji
     mount ::API::Branches
     mount ::API::Builds
diff --git a/lib/api/artifacts.rb b/lib/api/artifacts.rb
new file mode 100644
index 00000000000..6ce2bed8260
--- /dev/null
+++ b/lib/api/artifacts.rb
@@ -0,0 +1,34 @@
+module API
+  # Projects artifacts API
+  class Artifacts < Grape::API
+    before do
+      authenticate!
+      authorize!(:read_build, user_project)
+    end
+
+    resource :projects do
+      # Download the artifacts file from ref_name and build_name
+      #
+      # Parameters:
+      #   id (required) - The ID of a project
+      #   ref_name (required) - The ref from repository
+      #   build_name (required) - The name for the build
+      # Example Request:
+      #   GET /projects/:id/artifacts/:ref_name/:build_name
+      get ':id/artifacts/:ref_name/:build_name',
+          requirements: { ref_name: /.+/ } do
+        builds = user_project.builds_for(
+          params[:build_name], params[:ref_name])
+
+        latest_build = builds.success.latest.first
+
+        if latest_build
+          redirect(
+            "/projects/#{user_project.id}/builds/#{latest_build.id}/artifacts")
+        else
+          not_found!
+        end
+      end
+    end
+  end
+end
diff --git a/spec/requests/api/artifacts_spec.rb b/spec/requests/api/artifacts_spec.rb
new file mode 100644
index 00000000000..2b84c1a2072
--- /dev/null
+++ b/spec/requests/api/artifacts_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+require_relative '../shared/artifacts_context'
+
+describe API::API, api: true  do
+  include ApiHelpers
+
+  let(:user) { create(:user) }
+  let(:project) { create(:project) }
+  let(:pipeline) do
+    create(:ci_pipeline, project: project, sha: project.commit('fix').sha)
+  end
+  let(:build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) }
+
+  before do
+    project.team << [user, :developer]
+  end
+
+  describe 'GET /projects/:id/artifacts/:ref_name/:build_name' do
+    def path_from_ref(ref = pipeline.sha, build_name = build.name, _ = '')
+      api("/projects/#{project.id}/artifacts/#{ref}/#{build_name}", user)
+    end
+
+    context '401' do
+      let(:user) { nil }
+
+      before do
+        get path_from_ref
+      end
+
+      it 'gives 401 for unauthorized user' do
+        expect(response).to have_http_status(401)
+      end
+    end
+
+    context '404' do
+      def verify
+        expect(response).to have_http_status(404)
+      end
+
+      it_behaves_like 'artifacts from ref with 404'
+    end
+
+    context '302' do
+      def verify
+        expect(response).to redirect_to(
+          "/projects/#{project.id}/builds/#{build.id}/artifacts")
+      end
+
+      it_behaves_like 'artifacts from ref with 302'
+    end
+  end
+end
diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb
index 4c4bacfcbda..9722a9a1d64 100644
--- a/spec/requests/projects/artifacts_controller_spec.rb
+++ b/spec/requests/projects/artifacts_controller_spec.rb
@@ -1,4 +1,5 @@
 require 'spec_helper'
+require_relative '../shared/artifacts_context'
 
 describe Projects::ArtifactsController do
   let(:user) { create(:user) }
@@ -14,120 +15,43 @@ describe Projects::ArtifactsController do
   end
 
   describe 'GET /:project/artifacts/:ref/:build_name/browse' do
-    context '404' do
-      it 'has no such ref' do
-        get search_namespace_project_artifacts_path(
-          project.namespace,
-          project,
-          'TAIL',
-          build.name,
-          'browse')
+    def path_from_ref(ref = pipeline.sha, build_name = build.name,
+                      path = 'browse')
+      search_namespace_project_artifacts_path(
+        project.namespace,
+        project,
+        ref,
+        build_name,
+        path)
+    end
 
+    context '404' do
+      def verify
         expect(response.status).to eq(404)
       end
 
-      it 'has no such build' do
-        get search_namespace_project_artifacts_path(
-          project.namespace,
-          project,
-          pipeline.sha,
-          'NOBUILD',
-          'browse')
-
-        expect(response.status).to eq(404)
-      end
+      it_behaves_like 'artifacts from ref with 404'
 
-      it 'has no path' do
-        get search_namespace_project_artifacts_path(
-          project.namespace,
-          project,
-          pipeline.sha,
-          build.name,
-          '')
+      context 'has no path' do
+        before do
+          get path_from_ref(pipeline.sha, build.name, '')
+        end
 
-        expect(response.status).to eq(404)
+        it('gives 404') { verify }
       end
     end
 
     context '302' do
-      def path_from_ref(ref = pipeline.sha, build_name = build.name)
-        search_namespace_project_artifacts_path(
+      def verify
+        path = browse_namespace_project_build_artifacts_path(
           project.namespace,
           project,
-          ref,
-          build_name,
-          'browse')
-      end
-
-      shared_examples 'redirect to the build' do
-        it 'redirects' do
-          path = browse_namespace_project_build_artifacts_path(
-            project.namespace,
-            project,
-            build)
-
-          expect(response).to redirect_to(path)
-        end
-      end
-
-      context 'with sha' do
-        before do
-          get path_from_ref
-        end
-
-        it_behaves_like 'redirect to the build'
-      end
-
-      context 'with regular branch' do
-        before do
-          pipeline.update(sha: project.commit('master').sha)
-        end
-
-        before do
-          get path_from_ref('master')
-        end
-
-        it_behaves_like 'redirect to the build'
-      end
-
-      context 'with branch name containing slash' do
-        before do
-          pipeline.update(sha: project.commit('improve/awesome').sha)
-        end
+          build)
 
-        before do
-          get path_from_ref('improve/awesome')
-        end
-
-        it_behaves_like 'redirect to the build'
+        expect(response).to redirect_to(path)
       end
 
-      context 'with latest build' do
-        before do
-          3.times do # creating some old builds
-            create(:ci_build, :success, :artifacts, pipeline: pipeline)
-          end
-        end
-
-        before do
-          get path_from_ref
-        end
-
-        it_behaves_like 'redirect to the build'
-      end
-
-      context 'with success build' do
-        before do
-          build # make sure build was old, but still the latest success one
-          create(:ci_build, :pending, :artifacts, pipeline: pipeline)
-        end
-
-        before do
-          get path_from_ref
-        end
-
-        it_behaves_like 'redirect to the build'
-      end
+      it_behaves_like 'artifacts from ref with 302'
     end
   end
 end
diff --git a/spec/requests/shared/artifacts_context.rb b/spec/requests/shared/artifacts_context.rb
new file mode 100644
index 00000000000..4333be6e1cd
--- /dev/null
+++ b/spec/requests/shared/artifacts_context.rb
@@ -0,0 +1,78 @@
+shared_context 'artifacts from ref with 404' do
+  context 'has no such ref' do
+    before do
+      get path_from_ref('TAIL', build.name)
+    end
+
+    it('gives 404') { verify }
+  end
+
+  context 'has no such build' do
+    before do
+      get path_from_ref(pipeline.sha, 'NOBUILD')
+    end
+
+    it('gives 404') { verify }
+  end
+end
+
+shared_context 'artifacts from ref with 302' do
+  context 'with sha' do
+    before do
+      get path_from_ref
+    end
+
+    it('redirects') { verify }
+  end
+
+  context 'with regular branch' do
+    before do
+      pipeline.update(sha: project.commit('master').sha)
+    end
+
+    before do
+      get path_from_ref('master')
+    end
+
+    it('redirects') { verify }
+  end
+
+  context 'with branch name containing slash' do
+    before do
+      pipeline.update(sha: project.commit('improve/awesome').sha)
+    end
+
+    before do
+      get path_from_ref('improve/awesome')
+    end
+
+    it('redirects') { verify }
+  end
+
+  context 'with latest build' do
+    before do
+      3.times do # creating some old builds
+        create(:ci_build, :success, :artifacts, pipeline: pipeline)
+      end
+    end
+
+    before do
+      get path_from_ref
+    end
+
+    it('redirects') { verify }
+  end
+
+  context 'with success build' do
+    before do
+      build # make sure build was old, but still the latest success one
+      create(:ci_build, :pending, :artifacts, pipeline: pipeline)
+    end
+
+    before do
+      get path_from_ref
+    end
+
+    it('redirects') { verify }
+  end
+end
-- 
GitLab