Commit 57d4a172 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into 8-10-stable

parents 7ce5bb18 240a4aa6
......@@ -31,6 +31,7 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates.
- Fix fetching LFS objects for private CI projects
- Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown
......@@ -47,6 +48,7 @@ v 8.10.0 (unreleased)
- Render inline diffs for multiple changed lines following eachother
- Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Expose `due_date` for issues (Robert Schilling)
- API: Todos !3188 (Robert Schilling)
- API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings
......@@ -55,13 +57,16 @@ v 8.10.0 (unreleased)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets.
- PipelinesFinder uses git cache data
- Track a user who created a pipeline
- Actually render old and new sections of parallel diff next to each other
- Throttle the update of `project.pushes_since_gc` to 1 minute.
- Allow expanding and collapsing files in diff view (!4990)
- Collapse large diffs by default (!4990)
- Fix mentioned users list on diff notes
- Fix creation of deployment on build that is retried, redeployed or rollback
- Check for conflicts with existing Project's wiki path when creating a new project.
- Show last push widget in upstream after push to fork
- Fix stage status shown for pipelines
- Cache todos pending/done dashboard query counts.
- Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0
......@@ -82,7 +87,9 @@ v 8.10.0 (unreleased)
- Add basic system information like memory and disk usage to the admin panel
- Don't garbage collect commits that have related DB records like comments
- More descriptive message for git hooks and file locks
- Aliases of award emoji should be stored as original name. !5060 (dixpac)
- Handle custom Git hook result in GitLab UI
- Allow to access Container Registry for Public and Internal projects
- Allow '?', or '&' for label names
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
- Add date when user joined the team on the member page
......@@ -103,6 +110,7 @@ v 8.10.0 (unreleased)
- Fix issues importing projects from EE to CE
- Fix creating group with space in group path
- Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
- Limit the number of retries on error to 3 for exporting projects
v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154
......
......@@ -129,6 +129,8 @@
}
.cancel-retry-btns {
vertical-align: middle;
.btn:not(:first-child) {
margin-left: 8px;
}
......
......@@ -204,7 +204,8 @@ class Ability
:download_code,
:fork_project,
:read_commit_status,
:read_pipeline
:read_pipeline,
:read_container_image
]
end
......
......@@ -53,6 +53,7 @@ module Ci
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.user = user
new_build.environment = build.environment
new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build
......
......@@ -6,6 +6,8 @@ module Ci
self.table_name = 'ci_commits'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :user
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
......
......@@ -65,8 +65,7 @@ module Awardable
def create_award_emoji(name, current_user)
return unless emoji_awardable?
award_emoji.create(name: name, user: current_user)
award_emoji.create(name: normalize_name(name), user: current_user)
end
def remove_award_emoji(name, current_user)
......@@ -80,4 +79,10 @@ module Awardable
create_award_emoji(emoji_name, current_user)
end
end
private
def normalize_name(name)
Gitlab::AwardEmoji.normalize_emoji_name(name)
end
end
......@@ -229,8 +229,7 @@ class Note < ActiveRecord::Base
end
def award_emoji_name
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
Gitlab::AwardEmoji.normalize_emoji_name(original_name)
note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end
private
......
......@@ -162,7 +162,7 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
validates :import_url, addressable_url: true, if: :import_url
validates :import_url, addressable_url: true, if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :avatar_type,
......@@ -482,7 +482,7 @@ class Project < ActiveRecord::Base
end
def create_or_update_import_data(data: nil, credentials: nil)
return unless valid_import_url?
return unless import_url.present? && valid_import_url?
project_import_data = import_data || build_import_data
if data
......@@ -1038,8 +1038,8 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
def ensure_pipeline(sha, ref)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref)
def ensure_pipeline(sha, ref, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
......
......@@ -85,6 +85,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline'
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy
......
......@@ -2,6 +2,7 @@ module Ci
class CreatePipelineService < BaseService
def execute
pipeline = project.pipelines.new(params)
pipeline.user = current_user
unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found')
......
......@@ -14,7 +14,13 @@ class CreateCommitBuildsService
return false
end
@pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
@pipeline = Ci::Pipeline.new(
project: project,
sha: sha,
ref: ref,
before_sha: before_sha,
tag: tag,
user: user)
##
# Skip creating pipeline if no gitlab-ci.yml is found
......
......@@ -8,7 +8,6 @@ module Notes
if note.award_emoji?
noteable = note.noteable
todo_service.new_award_emoji(noteable, current_user)
return noteable.create_award_emoji(note.award_emoji_name, current_user)
end
......
......@@ -32,7 +32,7 @@
Cant find HEAD commit for this branch
- stages_status = pipeline.statuses.stages_status
- stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage|
%td
- status = stages_status[stage]
......@@ -58,16 +58,7 @@
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present?
.btn-group.inline
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to '#' do
= icon("play")
%span Deploy to production
.inline
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
......@@ -80,7 +71,7 @@
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
.cancel-retry-btns
.cancel-retry-btns.inline
- if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat")
......
class ProjectExportWorker
include Sidekiq::Worker
sidekiq_options queue: :gitlab_shell, retry: true
sidekiq_options queue: :gitlab_shell, retry: 3
def perform(current_user_id, project_id)
current_user = User.find(current_user_id)
......
class AddUserIdToPipeline < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :ci_commits, :user_id, :integer
end
end
class AddIndexForPipelineUserId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_concurrent_index :ci_commits, :user_id
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160712171823) do
ActiveRecord::Schema.define(version: 20160715134306) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -199,6 +199,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do
t.datetime "started_at"
t.datetime "finished_at"
t.integer "duration"
t.integer "user_id"
end
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
......@@ -210,6 +211,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree
create_table "ci_events", force: :cascade do |t|
t.integer "project_id"
......
......@@ -78,7 +78,8 @@ Example response:
"iid" : 6,
"labels" : [],
"subscribed" : false,
"user_notes_count": 1
"user_notes_count": 1,
"due_date": "2016-07-22"
}
]
```
......@@ -154,7 +155,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
"user_notes_count": 1
"user_notes_count": 1,
"due_date": null
}
]
```
......@@ -232,7 +234,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
"user_notes_count": 1
"user_notes_count": 1,
"due_date": "2016-07-22"
}
]
```
......@@ -295,7 +298,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed": false,
"user_notes_count": 1
"user_notes_count": 1,
"due_date": null
}
```
......@@ -320,6 +324,7 @@ POST /projects/:id/issues
| `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
......@@ -351,7 +356,8 @@ Example response:
"updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null,
"subscribed" : true,
"user_notes_count": 0
"user_notes_count": 0,
"due_date": null
}
```
......@@ -379,6 +385,7 @@ PUT /projects/:id/issues/:issue_id
| `labels` | string | no | Comma-separated label names for an issue |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
......@@ -410,7 +417,8 @@ Example response:
"assignee" : null,
"milestone" : null,
"subscribed" : true,
"user_notes_count": 0
"user_notes_count": 0,
"due_date": "2016-07-22"
}
```
......@@ -487,7 +495,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
}
},
"due_date": null
}
```
......@@ -541,7 +550,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
}
},
"due_date": null
}
```
......@@ -596,7 +606,8 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/orville"
},
"subscribed": false
"subscribed": false,
"due_date": null
}
```
......
......@@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
- ruby
test:postgres:
<< *job_definition
<<: *job_definition
services: *postgres_definition
test:mysql:
<< *job_definition
<<: *job_definition
services: *mysql_definition
```
......
......@@ -56,9 +56,9 @@ module API
not_found!('Award Emoji') unless can_read_awardable?
award = awardable.award_emoji.new(name: params[:name], user: current_user)
award = awardable.create_award_emoji(params[:name], current_user)
if award.save
if award.persisted?
present award, with: Entities::AwardEmoji
else
not_found!("Award Emoji #{award.errors.messages}")
......
......@@ -64,7 +64,7 @@ module API
ref = branches.first
end
pipeline = @project.ensure_pipeline(commit.sha, ref)
pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
......
......@@ -187,6 +187,7 @@ module API
end
expose :user_notes_count
expose :upvotes, :downvotes
expose :due_date
end
class ExternalIssue < Grape::Entity
......
......@@ -63,7 +63,12 @@ module API
if access_status.status
# Return the repository full path so that gitlab-shell has it when
# handling ssh commands
response[:repository_path] = project.repository.path_to_repo
response[:repository_path] =
if wiki?
project.wiki.repository.path_to_repo
else
project.repository.path_to_repo
end
end
response
......
......@@ -152,12 +152,13 @@ module API
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# POST /projects/:id/issues
post ":id/issues" do
post ':id/issues' do
required_attributes! [:title]
keys = [:title, :description, :assignee_id, :milestone_id]
keys = [:title, :description, :assignee_id, :milestone_id, :due_date]
keys << :created_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
......@@ -201,12 +202,13 @@ module API
# labels (optional) - The labels of an issue
# state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
put ':id/issues/:issue_id' do
issue = user_project.issues.find(params[:issue_id])
authorize! :update_issue, issue
keys = [:title, :description, :assignee_id, :milestone_id, :state_event]
keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
keys << :updated_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
......
......@@ -19,21 +19,22 @@ module Banzai
language = node.attr('class')
code = node.text
lexer = Rouge::Lexer.find_fancy(language)
css_classes = "code highlight"
lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText
formatter = Rouge::Formatters::HTML.new
css_classes = "code highlight js-syntax-highlight #{lexer.tag}"
begin
highlighted = ''
highlighted << %(<pre class="#{css_classes}"><code>)
highlighted << formatter.format(lexer.lex(code))
highlighted << %(</code></pre>)
code = formatter.format(lexer.lex(code))
css_classes << " js-syntax-highlight #{lexer.tag}"
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc.
highlighted = "<pre>#{code}</pre>"
end
highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
end
......
......@@ -63,7 +63,7 @@ module Grack
def ci_request?(login, password)
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
if project && matched_login.present? && git_cmd == 'git-upload-pack'
if project && matched_login.present?
underscored_service = matched_login['s'].underscore
if underscored_service == 'gitlab_ci'
......
......@@ -47,6 +47,8 @@ module Gitlab
end
def render_storage_upload_store_response(oid, size, tmp_file_name)
return render_forbidden unless tmp_file_name
render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name)
end
......
......@@ -74,8 +74,6 @@ module Gitlab
lfs.render_storage_upload_authorize_response(oid, size)
else
tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
return nil unless tmp_file_name
lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
end
end
......
......@@ -426,4 +426,23 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
......@@ -362,4 +362,23 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end
......@@ -426,4 +426,23 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
end
......@@ -3,15 +3,35 @@ require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
include FilterSpecHelper
it 'highlights valid code blocks' do
result = filter('<pre><code>def fun end</code>')
expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>")
context "when no language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>')
end
end
it 'passes through invalid code blocks' do
context "when a valid language is specified" do
it "highlights as that language" do
result = filter('<pre><code class="ruby">def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
end
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>')
end
end
context "when Rouge formatting fails" do
before do
allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError)
end
result = filter('<pre><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre>This is a test</pre>')
it "highlights as plaintext" do
result = filter('<pre><code class="ruby">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>')
end
end
end
......@@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do
context 'project import' do
it 'calls .from_project with no errors' do
project = create(:empty_project)
project.import_url = "ssh://git@bitbucket.org/test/test.git"
project.create_or_update_import_data(credentials:
{ user: "git",
password: nil,
bb_session: { bitbucket_access_token: "test",
bitbucket_access_token_secret: "test" } })
project.import_url = "ssh://git@bitbucket.org/test/test.git"
expect { described_class.from_project(project) }.not_to raise_error
end
......
......@@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status }
......
......@@ -177,10 +177,10 @@ describe CommitStatus, models: true do
describe '#stages' do
before do
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success'
create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
end
context 'stages list' do
......@@ -192,7 +192,7 @@ describe CommitStatus, models: true do
end
context 'stages with statuses' do
subject { CommitStatus.where(pipeline: pipeline).stages_status }
subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
it 'return list of stages with statuses' do
is_expected.to eq({
......@@ -201,6 +201,20 @@ describe CommitStatus, models: true do
'deploy' => 'running'
})
end
context 'when build is retried' do
before do
create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success'
end
it 'ignores a previous state' do
is_expected.to eq({
'build' => 'success',
'test' => 'success',
'deploy' => 'running'
})
end
end
end
end
......
......@@ -142,10 +142,10 @@ describe Project, models: true do
expect(project2).to be_valid
end
it 'does not allow to introduce an empty URI' do
it 'allows an empty URI' do
project2 = build(:project, import_url: '')
expect(project2).not_to be_valid
expect(project2).to be_valid
end
it 'does not produce import data on an empty URI' do
......
......@@ -31,6 +31,8 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:builds).dependent(:nullify) }
it { is_expected.to have_many(:pipelines).dependent(:nullify) }
describe '#group_members' do
it 'does not include group memberships for which user is a requester' do
......
......@@ -135,6 +135,22 @@ describe API::API, api: true do
expect(response).to have_http_status(401)
end
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
expect(issue.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end
end
......@@ -147,6 +163,22 @@ describe API::API, api: true do
expect(response).to have_http_status(201)
expect(json_response['user']['username']).to eq(user.username)
end
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
expect(note.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
......
......@@ -56,13 +56,21 @@ describe API::API, api: true do
context "git push with project.wiki" do
it 'responds with success' do
project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
project_wiki.team << [user, :developer]
push(key, project.wiki)
push(key, project_wiki)
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
end
end
context "git pull with project.wiki" do
it 'responds with success' do
pull(key, project.wiki)
expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
end
end
......
......@@ -503,6 +503,20 @@ describe API::API, api: true do
])
end
context 'with due date' do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
post api("/projects/#{project.id}/issues", user),
title: 'new issue', due_date: due_date
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
expect(json_response['due_date']).to eq(due_date)
end
end
context 'when an admin or owner makes the request' do
it 'accepts the creation date to be set' do
creation_time = 2.weeks.ago
......@@ -683,6 +697,17 @@ describe API::API, api: true do
end
end
describe 'PUT /projects/:id/issues/:issue_id to update due date' do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
put api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date
expect(response).to have_http_status(200)
expect(json_response['due_date']).to eq(due_date)
end
end
describe "DELETE /projects/:id/issues/:issue_id" do
it "rejects a non member from deleting an issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
......
require 'spec_helper'
describe Gitlab::Lfs::Router, lib: true do
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:forked_project) { fork_project(public_project, user) }
describe Gitlab::Lfs::Router do
let(:user) { create(:user) }
let(:user_two) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) }
let(:request) { Rack::Request.new(env) }
let(:env) do
let(:headers) do
{
'rack.input' => '',
'REQUEST_METHOD' => 'GET',
}
'Authorization' => authorization,
'X-Sendfile-Type' => sendfile
}.compact
end
let(:authorization) { }
let(:sendfile) { }
let(:lfs_router_auth) { new_lfs_router(project, user: user) }
let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) }
let(:lfs_router_noauth) { new_lfs_router(project) }
let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
let(:sample_oid) { lfs_object.oid }
let(:sample_size) { lfs_object.size }
describe 'when lfs is disabled' do
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
env['REQUEST_METHOD'] = 'POST'
body = {
let(:project) { create(:empty_project) }
let(:body) do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
......@@ -46,136 +29,171 @@ describe Gitlab::Lfs::Router, lib: true do
}
],
'operation' => 'upload'
}.to_json
env['rack.input'] = StringIO.new(body)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
}
end
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end
it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_disabled)
expect(response).to have_http_status(501)
expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.')
end
end
describe 'when fetching lfs object using deprecated API' do
describe 'deprecated API' do
let(:project) { create(:empty_project) }
before do
enable_lfs
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
end
shared_examples 'a deprecated' do
it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
expect(response).to have_http_status(501)
end
it 'returns deprecated message' do
expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.')
end
end
describe 'when fetching lfs object' do
context 'when fetching lfs object using deprecated API' do
let(:authorization) { authorize_user }
before do
enable_lfs
env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
end
describe 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do
it "responds with status 401" do
expect(lfs_router_noauth.try_call.first).to eq(401)
end
it_behaves_like 'a deprecated'
end
context 'with required headers' do
context 'when handling lfs request using deprecated API' do
before do
project.lfs_objects << lfs_object
env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
end
context 'when user does not have project access' do
it "responds with status 403" do
expect(lfs_router_auth.try_call.first).to eq(403)
it_behaves_like 'a deprecated'
end
end
context 'when user has project access' do
describe 'when fetching lfs object' do
let(:project) { create(:empty_project) }
let(:update_permissions) { }
before do
project.team << [user, :master]
enable_lfs
update_permissions
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
end
it "responds with status 200" do
expect(lfs_router_auth.try_call.first).to eq(200)
context 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do
it 'responds with status 401' do
expect(response).to have_http_status(401)
end
it "responds with the file location" do
expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
end
context 'with required headers' do
shared_examples 'responds with a file' do
let(:sendfile) { 'X-Sendfile' }
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
context 'when CI is authorized' do
it "responds with status 200" do
expect(lfs_router_ci_auth.try_call.first).to eq(200)
it 'responds with the file location' do
expect(response.headers['Content-Type']).to eq('application/octet-stream')
expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
end
end
it "responds with the file location" do
expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
context 'with user is authorized' do
let(:authorization) { authorize_user }
context 'and does not have project access' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it 'responds with status 403' do
expect(response).to have_http_status(403)
end
end
context 'without required headers' do
it "responds with status 403" do
expect(lfs_router_auth.try_call.first).to eq(403)
context 'and does have project access' do
let(:update_permissions) do
project.team << [user, :master]
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end
end
context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
let(:update_permissions) do
project.lfs_objects << lfs_object
end
describe 'when handling lfs request using deprecated API' do
before do
enable_lfs
env['REQUEST_METHOD'] = 'POST'
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
it_behaves_like 'responds with a file'
end
end
it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
context 'without required headers' do
let(:authorization) { authorize_user }
it 'responds with status 403' do
expect(response).to have_http_status(403)
end
end
end
end
describe 'when handling lfs batch request' do
let(:update_lfs_permissions) { }
let(:update_user_permissions) { }
before do
enable_lfs
env['REQUEST_METHOD'] = 'POST'
env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
update_lfs_permissions
update_user_permissions
post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end
describe 'download' do
before do
body = { 'operation' => 'download',
let(:project) { create(:empty_project) }
let(:body) do
{ 'operation' => 'download',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size
}]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
shared_examples 'an authorized requests' do
context 'when downloading an lfs object that is assigned to our project' do
before do
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
it 'responds with status 200 and href to download' do
response = router.try_call
expect(response.first).to eq(200)
response_body = ActiveSupport::JSON.decode(response.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(response_body).to eq('objects' => [
it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid,
'size' => sample_size,
'actions' => {
'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth }
'header' => { 'Authorization' => authorization }
}
}
}])
......@@ -183,16 +201,17 @@ describe Gitlab::Lfs::Router, lib: true do
end
context 'when downloading an lfs object that is assigned to other project' do
before do
public_project.lfs_objects << lfs_object
let(:other_project) { create(:empty_project) }
let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end
it 'responds with status 200 and error message' do
response = router.try_call
expect(response.first).to eq(200)
response_body = ActiveSupport::JSON.decode(response.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(response_body).to eq('objects' => [
it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid,
'size' => sample_size,
'error' => {
......@@ -204,22 +223,21 @@ describe Gitlab::Lfs::Router, lib: true do
end
context 'when downloading a lfs object that does not exist' do
before do
body = { 'operation' => 'download',
let(:body) do
{ 'operation' => 'download',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
}]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
it "responds with status 200 and error message" do
response = router.try_call
expect(response.first).to eq(200)
response_body = ActiveSupport::JSON.decode(response.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(response_body).to eq('objects' => [
it 'with an 404 for specific object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078,
'error' => {
......@@ -231,8 +249,8 @@ describe Gitlab::Lfs::Router, lib: true do
end
context 'when downloading one new and one existing lfs object' do
before do
body = { 'operation' => 'download',
let(:body) do
{ 'operation' => 'download',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
......@@ -241,17 +259,19 @@ describe Gitlab::Lfs::Router, lib: true do
'size' => sample_size
}
]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
it "responds with status 200 with upload hypermedia link for the new object" do
response = router.try_call
expect(response.first).to eq(200)
response_body = ActiveSupport::JSON.decode(response.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(response_body).to eq('objects' => [
it 'responds with upload hypermedia link for the new object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078,
'error' => {
......@@ -264,7 +284,7 @@ describe Gitlab::Lfs::Router, lib: true do
'actions' => {
'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth }
'header' => { 'Authorization' => authorization }
}
}
}])
......@@ -273,23 +293,21 @@ describe Gitlab::Lfs::Router, lib: true do
end
context 'when user is authenticated' do
let(:auth) { authorize(user) }
let(:authorization) { authorize_user }
before do
env["HTTP_AUTHORIZATION"] = auth
let(:update_user_permissions) do
project.team << [user, role]
end
it_behaves_like 'an authorized requests' do
let(:role) { :reporter }
let(:router) { lfs_router_auth }
end
context 'when user does is not member of the project' do
let(:role) { :guest }
it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403)
expect(response).to have_http_status(403)
end
end
......@@ -297,40 +315,36 @@ describe Gitlab::Lfs::Router, lib: true do
let(:role) { :guest }
it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403)
expect(response).to have_http_status(403)
end
end
end
context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' }
let(:authorization) { authorize_ci_project }
before do
env["HTTP_AUTHORIZATION"] = auth
end
it_behaves_like 'an authorized requests' do
let(:router) { lfs_router_ci_auth }
end
it_behaves_like 'an authorized requests'
end
context 'when user is not authenticated' do
describe 'is accessing public project' do
before do
public_project.lfs_objects << lfs_object
let(:project) { create(:project, :public) }
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
it 'responds with status 200 and href to download' do
response = lfs_router_public_noauth.try_call
expect(response.first).to eq(200)
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response).to have_http_status(200)
end
expect(response_body).to eq('objects' => [
it 'responds with status 200 and href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid,
'size' => sample_size,
'actions' => {
'download' => {
'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => {}
}
}
......@@ -339,83 +353,83 @@ describe Gitlab::Lfs::Router, lib: true do
end
describe 'is accessing non-public project' do
before do
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
it 'responds with authorization required' do
expect(lfs_router_noauth.try_call.first).to eq(401)
expect(response).to have_http_status(401)
end
end
end
end
describe 'upload' do
before do
body = { 'operation' => 'upload',
let(:project) { create(:project, :public) }
let(:body) do
{ 'operation' => 'upload',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size
}]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
describe 'when request is authenticated' do
describe 'when user has project push access' do
before do
@auth = authorize(user)
env["HTTP_AUTHORIZATION"] = @auth
let(:authorization) { authorize_user }
let(:update_user_permissions) do
project.team << [user, :developer]
end
context 'when pushing an lfs object that already exists' do
before do
public_project.lfs_objects << lfs_object
let(:other_project) { create(:empty_project) }
let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end
it "responds with status 200 and links the object to the project" do
response_body = lfs_router_auth.try_call.last
response = ActiveSupport::JSON.decode(response_body.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(response['objects']).to be_kind_of(Array)
expect(response['objects'].first['oid']).to eq(sample_oid)
expect(response['objects'].first['size']).to eq(sample_size)
it 'responds with links the object to the project' do
expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first['oid']).to eq(sample_oid)
expect(json_response['objects'].first['size']).to eq(sample_size)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
end
end
context 'when pushing a lfs object that does not exist' do
before do
body = { 'operation' => 'upload',
let(:body) do
{ 'operation' => 'upload',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
}]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
it "responds with status 200 and upload hypermedia link" do
response = lfs_router_auth.try_call
expect(response.first).to eq(200)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
it 'responds with upload hypermedia link' do
expect(json_response['objects']).to be_kind_of(Array)
expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(json_response['objects'].first['size']).to eq(1575078)
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
end
end
context 'when pushing one new and one existing lfs object' do
before do
body = { 'operation' => 'upload',
let(:body) do
{ 'operation' => 'upload',
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
......@@ -424,87 +438,89 @@ describe Gitlab::Lfs::Router, lib: true do
'size' => sample_size
}
]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end
it "responds with status 200 with upload hypermedia link for the new object" do
response = lfs_router_auth.try_call
expect(response.first).to eq(200)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body['objects']).to be_kind_of(Array)
it 'responds with upload hypermedia link for the new object' do
expect(json_response['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078)
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth)
expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(json_response['objects'].first['size']).to eq(1575078)
expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization)
expect(response_body['objects'].last['oid']).to eq(sample_oid)
expect(response_body['objects'].last['size']).to eq(sample_size)
expect(response_body['objects'].last).not_to have_key('actions')
expect(json_response['objects'].last['oid']).to eq(sample_oid)
expect(json_response['objects'].last['size']).to eq(sample_size)
expect(json_response['objects'].last).not_to have_key('actions')
end
end
end
context 'when user does not have push access' do
let(:authorization) { authorize_user }
it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403)
expect(response).to have_http_status(403)
end
end
context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
it 'responds with 401' do
expect(lfs_router_ci_auth.try_call.first).to eq(401)
expect(response).to have_http_status(401)
end
end
end
context 'when user is not authenticated' do
context 'when user has push access' do
before do
let(:update_user_permissions) do
project.team << [user, :master]
end
it "responds with status 401" do
expect(lfs_router_public_noauth.try_call.first).to eq(401)
it 'responds with status 401' do
expect(response).to have_http_status(401)
end
end
context 'when user does not have push access' do
it "responds with status 401" do
expect(lfs_router_public_noauth.try_call.first).to eq(401)
it 'responds with status 401' do
expect(response).to have_http_status(401)
end
end
end
context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' }
let(:authorization) { authorize_ci_project }
before do
env["HTTP_AUTHORIZATION"] = auth
end
it "responds with status 403" do
expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
it 'responds with status 403' do
expect(response).to have_http_status(401)
end
end
end
describe 'unsupported' do
before do
body = { 'operation' => 'other',
let(:project) { create(:empty_project) }
let(:body) do
{ 'operation' => 'other',
'objects' => [
{ 'oid' => sample_oid,
'size' => sample_size
}]
}.to_json
env['rack.input'] = StringIO.new(body)
}
end
it 'responds with status 404' do
expect(lfs_router_public_noauth.try_call.first).to eq(404)
expect(response).to have_http_status(404)
end
end
end
......@@ -512,38 +528,36 @@ describe Gitlab::Lfs::Router, lib: true do
describe 'when pushing a lfs object' do
before do
enable_lfs
env['REQUEST_METHOD'] = 'PUT'
end
shared_examples 'unauthorized' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(router.project)
put_authorize
end
it 'responds with status 401' do
expect(router.try_call.first).to eq(401)
expect(response).to have_http_status(401)
end
end
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
headers_for_upload_finalize(router.project)
put_finalize
end
it 'responds with status 401' do
expect(router.try_call.first).to eq(401)
expect(response).to have_http_status(401)
end
end
context 'and request is sent with a malformed headers' do
before do
env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
put_finalize('cat /etc/passwd')
end
it 'does not recognize it as a valid lfs command' do
expect(router.try_call).to eq(nil)
expect(response).to have_http_status(403)
end
end
end
......@@ -551,27 +565,31 @@ describe Gitlab::Lfs::Router, lib: true do
shared_examples 'forbidden' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(router.project)
put_authorize
end
it 'responds with 403' do
expect(router.try_call.first).to eq(403)
expect(response).to have_http_status(403)
end
end
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
headers_for_upload_finalize(router.project)
put_finalize
end
it 'responds with 403' do
expect(router.try_call.first).to eq(403)
expect(response).to have_http_status(403)
end
end
end
describe 'to one project' do
let(:project) { create(:empty_project) }
describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do
before do
project.team << [user, :developer]
......@@ -579,13 +597,14 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(project)
put_authorize
end
it 'responds with status 200, location of lfs store and object details' do
json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(lfs_router_auth.try_call.first).to eq(200)
it 'responds with status 200, location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size)
......@@ -594,54 +613,58 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
headers_for_upload_finalize(project)
put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
it 'responds with status 200 and lfs object is linked to the project' do
expect(lfs_router_auth.try_call.first).to eq(200)
it 'lfs object is linked to the project' do
expect(lfs_object.projects.pluck(:id)).to include(project.id)
end
end
end
describe 'and user does not have push access' do
let(:router) { lfs_router_auth }
it_behaves_like 'forbidden'
end
end
context 'when CI is authenticated' do
let(:router) { lfs_router_ci_auth }
let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized'
end
context 'for unauthenticated' do
let(:router) { new_lfs_router(project) }
it_behaves_like 'unauthorized'
end
end
describe 'to a forked project' do
let(:forked_project) { fork_project(public_project, user) }
let(:upstream_project) { create(:project, :public) }
let(:project_owner) { create(:user) }
let(:project) { fork_project(upstream_project, project_owner) }
describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do
before do
forked_project.team << [user_two, :developer]
project.team << [user, :developer]
end
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
header_for_upload_authorize(forked_project)
put_authorize
end
it 'responds with status 200, location of lfs store and object details' do
json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
expect(lfs_router_forked_auth.try_call.first).to eq(200)
it 'with location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size)
......@@ -650,81 +673,96 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
headers_for_upload_finalize(forked_project)
put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
it 'responds with status 200 and lfs object is linked to the source project' do
expect(lfs_router_forked_auth.try_call.first).to eq(200)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
it 'lfs object is linked to the source project' do
expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
end
end
end
describe 'and user does not have push access' do
let(:router) { lfs_router_forked_auth }
it_behaves_like 'forbidden'
end
end
context 'when CI is authenticated' do
let(:router) { lfs_router_forked_ci_auth }
let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized'
end
context 'for unauthenticated' do
let(:router) { lfs_router_forked_noauth }
it_behaves_like 'unauthorized'
end
describe 'and second project not related to fork or a source project' do
let(:second_project) { create(:project) }
let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) }
let(:second_project) { create(:empty_project) }
let(:authorization) { authorize_user }
before do
public_project.lfs_objects << lfs_object
headers_for_upload_finalize(second_project)
second_project.team << [user, :master]
upstream_project.lfs_objects << lfs_object
end
context 'when pushing the same lfs object to the second project' do
before do
second_project.team << [user, :master]
put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
end
it 'responds with 200 and links the lfs object to the project' do
expect(lfs_router_second_project.try_call.first).to eq(200)
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id)
it 'responds with status 200' do
expect(response).to have_http_status(200)
end
it 'links the lfs object to the project' do
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
end
end
end
end
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
def put_authorize
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
end
def authorize(user)
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
def put_finalize(lfs_tmp = lfs_tmp_file)
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
end
def new_lfs_router(project, user: nil, ci: false)
Gitlab::Lfs::Router.new(project, user, ci, request)
def lfs_tmp_file
"#{sample_oid}012345678"
end
end
def header_for_upload_authorize(project)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize"
def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
end
def headers_for_upload_finalize(project)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
def authorize_user
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
def fork_project(project, user, object = nil)
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute
end
def post_json(url, body = nil, headers = nil)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
end
def json_response
@json_response ||= JSON.parse(response.body)
end
end
......@@ -87,9 +87,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
context 'user authorization' do
let(:project) { create(:project) }
let(:current_user) { create(:user) }
context 'for private project' do
let(:project) { create(:empty_project) }
context 'allow to use scope-less authentication' do
it_behaves_like 'a valid token'
end
......@@ -135,6 +137,58 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
end
context 'for public project' do
let(:project) { create(:empty_project, :public) }
context 'allow anyone to pull images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull" }
end
it_behaves_like 'a pullable'
end
context 'disallow anyone to push images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push" }
end
it_behaves_like 'an inaccessible'
end
end
context 'for internal project' do
let(:project) { create(:empty_project, :internal) }
context 'for internal user' do
context 'allow anyone to pull images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull" }
end
it_behaves_like 'a pullable'
end
context 'disallow anyone to push images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push" }
end
it_behaves_like 'an inaccessible'
end
end
context 'for external user' do
let(:current_user) { create(:user, external: true) }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull,push" }
end
it_behaves_like 'an inaccessible'
end
end
end
context 'project authorization' do
let(:current_project) { create(:empty_project) }
......
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe CreateCommitBuildsService, services: true do
let(:service) { CreateCommitBuildsService.new }
let(:project) { FactoryGirl.create(:empty_project) }
let(:user) { nil }
let(:user) { create(:user) }
before do
stub_ci_pipeline_to_return_yaml_file
......@@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do
it { expect(pipeline).to be_valid }
it { expect(pipeline).to be_persisted }
it { expect(pipeline).to eq(project.pipelines.last) }
it { expect(pipeline).to have_attributes(user: user) }
it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
end
......
......@@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do
expect_any_instance_of(described_class).to receive(:execute)
subject
end
it 'is set as deployable' do
subject
expect(Deployment.last.deployable).to eq(deployable)
end
end
context 'without environment specified' do
......@@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do
context 'when build succeeds' do
it_behaves_like 'does create environment and deployment' do
let(:deployable) { build }
subject { build.success }
end
end
......@@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do
subject { build.drop }
end
end
context 'when build is retried' do
it_behaves_like 'does create environment and deployment' do
let(:deployable) { Ci::Build.retry(build) }
subject { deployable.success }
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment