Commit fc03cb11 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 6f83af4d ecb05e59
......@@ -162,7 +162,8 @@ export default {
removeWIPPath: store.removeWIPPath,
sourceBranchPath: store.sourceBranchPath,
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
statusPath: store.statusPath,
mergeRequestBasicPath: store.mergeRequestBasicPath,
mergeRequestWidgetPath: store.mergeRequestWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
};
......
......@@ -30,11 +30,11 @@ export default class MRWidgetService {
}
poll() {
return axios.get(`${this.endpoints.statusPath}?serializer=basic`);
return axios.get(this.endpoints.mergeRequestBasicPath);
}
checkStatus() {
return axios.get(`${this.endpoints.statusPath}?serializer=widget`);
return axios.get(this.endpoints.mergeRequestWidgetPath);
}
fetchMergeActionsContent() {
......
......@@ -86,7 +86,8 @@ export default class MergeRequestStore {
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
this.statusPath = data.status_path;
this.mergeRequestBasicPath = data.merge_request_basic_path;
this.mergeRequestWidgetPath = data.merge_request_widget_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
this.newBlobPath = data.new_blob_path;
......
......@@ -51,6 +51,13 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
Ci::Pipeline.none
end
end
def close_merge_request_if_no_source_project
return if @merge_request.source_project
return unless @merge_request.open?
@merge_request.close
end
end
Projects::MergeRequests::ApplicationController.prepend(EE::Projects::MergeRequests::ApplicationController)
# frozen_string_literal: true
class Projects::MergeRequests::ContentController < Projects::MergeRequests::ApplicationController
# @merge_request.check_mergeability is not executed here since
# widget serializer calls it via mergeable? method
# but we might want to call @merge_request.check_mergeability
# for other types of serialization
before_action :close_merge_request_if_no_source_project
around_action :allow_gitaly_ref_name_caching
def widget
respond_to do |format|
format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000)
serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
render json: serializer.represent(merge_request, serializer: 'widget')
end
end
end
end
......@@ -235,12 +235,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end
def close_merge_request_if_no_source_project
if !@merge_request.source_project && @merge_request.open?
@merge_request.close
end
end
private
def ci_environments_status_on_merge_result?
......
# frozen_string_literal: true
module Types
class CommitType < BaseObject
graphql_name 'Commit'
authorize :download_code
present_using CommitPresenter
field :id, type: GraphQL::ID_TYPE, null: false
field :sha, type: GraphQL::STRING_TYPE, null: false
field :title, type: GraphQL::STRING_TYPE, null: true
field :description, type: GraphQL::STRING_TYPE, null: true
field :message, type: GraphQL::STRING_TYPE, null: true
field :authored_date, type: Types::TimeType, null: true
field :web_url, type: GraphQL::STRING_TYPE, null: false
# models/commit lazy loads the author by email
field :author, type: Types::UserType, null: true
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
description: "Latest pipeline for this commit",
resolve: -> (obj, ctx, args) do
Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
end
end
end
......@@ -4,6 +4,11 @@ module Types
class TreeType < BaseObject
graphql_name 'Tree'
# Complexity 10 as it triggers a Gitaly call on each render
field :last_commit, Types::CommitType, null: true, complexity: 10, resolve: -> (tree, args, ctx) do
tree.repository.last_commit_for_path(tree.sha, tree.path)
end
field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
end
......
......@@ -295,6 +295,11 @@ module Ci
end
end
def self.latest_for_shas(shas)
max_id_per_sha = for_sha(shas).group(:sha).select("max(id)")
where(id: max_id_per_sha)
end
def self.latest_successful_ids_per_project
success.group(:project_id).select('max(id) as id')
end
......
......@@ -25,7 +25,7 @@ module RelativePositioning
relative_position = position_between(max_relative_position, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
object.save
object.save(touch: false)
end
end
end
......@@ -159,7 +159,7 @@ module RelativePositioning
def save_positionable_neighbours
return unless @positionable_neighbours
status = @positionable_neighbours.all?(&:save)
status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
@positionable_neighbours = nil
status
......
# frozen_string_literal: true
class CommitPresenter < Gitlab::View::Presenter::Simple
class CommitPresenter < Gitlab::View::Presenter::Delegated
include GlobalID::Identification
presents :commit
def status_for(ref)
......@@ -10,4 +12,8 @@ class CommitPresenter < Gitlab::View::Presenter::Simple
def any_pipelines?
can?(current_user, :read_pipeline, commit.project) && commit.pipelines.any?
end
def web_url
Gitlab::UrlBuilder.new(commit).url
end
end
......@@ -219,8 +219,12 @@ class MergeRequestWidgetEntity < IssuableEntity
project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
expose :status_path do |merge_request|
project_merge_request_path(merge_request.target_project, merge_request, format: :json)
expose :merge_request_basic_path do |merge_request|
project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
end
expose :merge_request_widget_path do |merge_request|
widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :ci_environments_status_path do |merge_request|
......
......@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService
end
if issuable.changed? || params.present?
issuable.assign_attributes(params.merge(updated_by: current_user))
issuable.assign_attributes(params)
if has_title_or_description_changed?(issuable)
issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user)
......@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService
before_update(issuable)
# Do not touch when saving the issuable if only changes position within a list. We should call
# this method at this point to capture all possible changes.
should_touch = update_timestamp?(issuable)
issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
if issuable.with_transaction_returning_status { issuable.save }
if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
......@@ -402,6 +407,10 @@ class IssuableBaseService < BaseService
def ensure_milestone_available(issuable)
issuable.milestone_id = nil unless issuable.milestone_available?
end
def update_timestamp?(issuable)
issuable.changes.keys != ["relative_position"]
end
end
IssuableBaseService.prepend(EE::IssuableBaseService)
---
title: Will not update issue timestamps when changing positions in a list
merge_request: 29677
author:
type: changed
---
title: Added commit type to tree GraphQL response
merge_request: 29412
author:
type: added
---
title: Add a separate endpoint for fetching MRs serialized as widgets
merge_request: 29979
author:
type: performance
---
title: Support CIDR notation in IP rate limiter
merge_request: 30146
author:
type: changed
......@@ -261,6 +261,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :commits
get :pipelines
get :diffs, to: 'merge_requests/diffs#show'
get :widget, to: 'merge_requests/content#widget'
end
get :diff_for_path, controller: 'merge_requests/diffs'
......
......@@ -53,8 +53,9 @@ For more information on how to use these options check out
The following settings can be configured:
- `enabled`: By default this is set to `false`. Set this to `true` to enable Rack Attack.
- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a ruby array.
For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`.
- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a Ruby array.
CIDR notation is supported in GitLab v12.1 and up.
For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3", "192.168.0.1/24"]`.
- `maxretry`: The maximum amount of times a request can be made in the
specified time.
- `findtime`: The maximum amount of time that failed requests can count against an IP
......
......@@ -206,6 +206,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
## Troubleshooting
### docker: Error response from daemon: failed to copy xattrs
......
......@@ -259,3 +259,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
......@@ -404,6 +404,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
## Dependency List
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
......
......@@ -10,7 +10,7 @@ high-level view on projects and groups, and start remediation processes when nee
GitLab can scan and report any vulnerabilities found in your project.
| Secure scanning tools | Description |
| Secure scanning tool | Description |
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](container_scanning/index.md) **[ULTIMATE]** | Scan Docker containers for known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
......@@ -19,6 +19,29 @@ GitLab can scan and report any vulnerabilities found in your project.
| [Security Dashboard](security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. |
## Maintenance and update of the vulnerabilities database
The various scanning tools and the vulnerabilities database are updated regularly.
| Secure scanning tool | Vulnerabilities database updates |
|:-------------------------------------------------------------|-------------------------------------------|
| [Container Scanning](container_scanning/index.md) | Uses `clair` underneath and the latest `clair-db` version is used for each job run by running the [`latest` docker image tag](https://gitlab.com/gitlab-org/gitlab-ee/blob/438a0a56dc0882f22bdd82e700554525f552d91b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml#L37). The `clair-db` database [is updated daily according to the author](https://github.com/arminc/clair-local-scan#clair-server-or-local). |
| [Dependency Scanning](dependency_scanning/index.md) | Relies on `bundler-audit` (for Rubygems), `retire.js` (for NPM packages) and `gemnasium` (GitLab's own tool for all libraries). `bundler-audit` and `retire.js` both fetch their vulnerabilities data from GitHub repositories, so vulnerabilities added to `ruby-advisory-db` and `retire.js` are immediately available. The tools themselves are updated once per month if there's a new version. The [Gemnasium DB](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is updated at least once a week. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) | Updated weekly on Sundays. The underlying tool, `zaproxy`, downloads fresh rules at startup. |
| [Static Application Security Testing (SAST)](sast/index.md) | Relies exclusively on [the tools GitLab is wrapping](sast/index.md#supported-languages-and-frameworks). The underlying analyzers are updated at least once per month if a relevant update is available. The vulnerabilities database is updated by the upstream tools. |
You don't have to update GitLab to benefit from the latest vulnerabilities definitions,
but you may have to in the future.
The security tools are released as Docker images, and the vendored job definitions
to enable them are using the `x-y-stable` image tags that get overridden each time a new
release of the tools is pushed. The Docker images are updated to match the
previous GitLab releases, so they automatically get the latest versions of the
scanning tools without the user having to do anything.
This workflow comes with some drawbacks and there's a
[plan to change this](https://gitlab.com/gitlab-org/gitlab-ee/issues/9725).
## Interacting with the vulnerabilities
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.8.
......
......@@ -269,7 +269,7 @@ it highlighted:
"url": "https://cwe.mitre.org/data/definitions/330.html"
}
]
},
},
{
"category": "sast",
"message": "Probable insecure usage of temp file/directory.",
......@@ -296,7 +296,7 @@ it highlighted:
"url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
}
]
},
},
],
"remediations": []
}
......@@ -320,7 +320,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].scanner` | A node that describes the analyzer used to find this vulnerability. |
| `vulnerabilities[].scanner.id` | Id of the scanner as a snake_case string. |
| `vulnerabilities[].scanner.name` | Name of the scanner, for display purposes. |
| `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
| `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
| `vulnerabilities[].location.file` | Path to the file where the vulnerability is located. Optional. |
| `vulnerabilities[].location.start_line` | The first line of the code affected by the vulnerability. Optional. |
| `vulnerabilities[].location.end_line` | The last line of the code affected by the vulnerability. Optional. |
......@@ -330,7 +330,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].identifiers[].type` | Type of the identifier. Possible values: common identifier types (among `cve`, `cwe`, `osvdb`, and `usn`) or analyzer-dependent ones (e.g., `bandit_test_id` for [Bandit analyzer](https://wiki.openstack.org/wiki/Security/Projects/Bandit)). |
| `vulnerabilities[].identifiers[].name` | Name of the identifier for display purposes. |
| `vulnerabilities[].identifiers[].value` | Value of the identifier for matching purposes. |
| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
## Secret detection
......@@ -363,3 +363,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
## Vulnerabilities database update
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
......@@ -3,6 +3,8 @@
module Gitlab
module Auth
class IpRateLimiter
include ::Gitlab::Utils::StrongMemoize
attr_reader :ip
def initialize(ip)
......@@ -37,7 +39,20 @@ module Gitlab
end
def ip_can_be_banned?
config.ip_whitelist.exclude?(ip)
!trusted_ip?
end
def trusted_ip?
trusted_ips.any? { |netmask| netmask.include?(ip) }
end
def trusted_ips
strong_memoize(:trusted_ips) do
config.ip_whitelist.map do |proxy|
IPAddr.new(proxy)
rescue IPAddr::InvalidAddressError
end.compact
end
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class PipelineForShaLoader
attr_accessor :project, :sha
def initialize(project, sha)
@project, @sha = project, sha
end
def find_last
BatchLoader.for(sha).batch(key: project) do |shas, loader, args|
pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
pipelines.each do |pipeline|
loader.call(pipeline.sha, pipeline)
end
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Projects::MergeRequests::ContentController do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
before do
sign_in(user)
end
def do_request
get :widget, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid,
format: :json
}
end
describe 'GET widget' do
context 'user has access to the project' do
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
project.add_maintainer(user)
end
it 'renders widget MR entity as json' do
do_request
expect(response).to match_response_schema('entities/merge_request_widget')
end
it 'checks whether the MR can be merged' do
controller.instance_variable_set(:@merge_request, merge_request)
expect(merge_request).to receive(:check_mergeability)
do_request
end
it 'closes an MR with moved source project' do
merge_request.update_column(:source_project_id, nil)
expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
end
end
context 'user does not have access to the project' do
it 'renders widget MR entity as json' do
do_request
expect(response).to have_http_status(:not_found)
end
end
end
end
......@@ -99,7 +99,8 @@
"revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
"status_path": { "type": "string" },
"merge_request_basic_path": { "type": "string" },
"merge_request_widget_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
......
......@@ -113,7 +113,7 @@ describe GitlabSchema do
end
it "raises a meaningful error if a global id couldn't be generated" do
expect { described_class.id_from_object(build(:commit)) }
expect { described_class.id_from_object(build(:wiki_directory)) }
.to raise_error(RuntimeError, /include `GlobalID::Identification` into/i)
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe GitlabSchema.types['Commit'] do
it { expect(described_class.graphql_name).to eq('Commit') }
it { expect(described_class).to require_graphql_authorizations(:download_code) }
it { expect(described_class).to have_graphql_fields(:id, :sha, :title, :description, :message, :authored_date, :author, :web_url, :latest_pipeline) }
end
......@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::TreeType do
it { expect(described_class.graphql_name).to eq('Tree') }
it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) }
it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) }
end
......@@ -218,7 +218,8 @@ export default {
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff',
status_path: '/root/acets-app/merge_requests/22.json',
merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic',
merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false,
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
let(:ip) { '10.2.2.3' }
let(:whitelist) { ['127.0.0.1'] }
let(:options) do
{
enabled: true,
ip_whitelist: whitelist,
bantime: 1.minute,
findtime: 1.minute,
maxretry: 2
}
end
subject { described_class.new(ip) }
before do
stub_rack_attack_setting(options)
end
after do
subject.reset!
end
describe '#register_fail!' do
it 'bans after 3 consecutive failures' do
expect(subject.banned?).to be_falsey
3.times { subject.register_fail! }
expect(subject.banned?).to be_truthy
end
shared_examples 'whitelisted IPs' do
it 'does not ban after max retry limit' do
expect(subject.banned?).to be_falsey
3.times { subject.register_fail! }
expect(subject.banned?).to be_falsey
end
end
context 'with a whitelisted netmask' do
before do
options[:ip_whitelist] = ['127.0.0.1', '10.2.2.0/24', 'bad']
stub_rack_attack_setting(options)
end
it_behaves_like 'whitelisted IPs'
end
context 'with a whitelisted IP' do
before do
options[:ip_whitelist] = ['10.2.2.3']
stub_rack_attack_setting(options)
end
it_behaves_like 'whitelisted IPs'
end
end
end
require 'spec_helper'
describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
include GraphqlHelpers
describe '#find_last' do
it 'batch-resolves latest pipeline' do
project = create(:project, :repository)
pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
result = batch(max_queries: 1) do
[pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
end
expect(result).to contain_exactly(pipeline2, pipeline3)
end
end
end
......@@ -1886,6 +1886,17 @@ describe Ci::Pipeline, :mailer do
end
end
describe '.latest_for_shas' do
let(:sha) { 'abc' }
it 'returns latest pipeline for sha' do
create(:ci_pipeline, sha: sha)
pipeline2 = create(:ci_pipeline, sha: sha)
expect(described_class.latest_for_shas(sha)).to contain_exactly(pipeline2)
end
end
describe '.latest_successful_ids_per_project' do
let(:projects) { create_list(:project, 2) }
let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) }
......
......@@ -33,6 +33,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end
it 'returns null commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['last_commit']).to be_nil
end
end
context 'when ref does not exist' do
......@@ -45,6 +51,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end
it 'returns null commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['last_commit']).to be_nil
end
end
context 'when ref and path exist' do
......@@ -61,6 +73,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0
expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0
end
it 'returns tree latest commit' do
post_graphql(query, current_user: current_user)
expect(graphql_data['project']['repository']['tree']['lastCommit']).to be_present
end
end
context 'when current user is nil' do
......
......@@ -95,6 +95,11 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end
def stub_rack_attack_setting(messages)
allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
end
private
# Modifies stubbed messages to also stub possible predicate versions
......
shared_examples 'issues move service' do |group|
shared_examples 'updating timestamps' do
it 'updates updated_at' do
expect {described_class.new(parent, user, params).execute(issue)}
.to change {issue.reload.updated_at}
end
end
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
it_behaves_like 'updating timestamps'
it 'delegates the label changes to Issues::UpdateService' do
service = double(:service)
expect(Issues::UpdateService).to receive(:new).and_return(service)
......@@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
it_behaves_like 'updating timestamps'
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
......@@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } }
it_behaves_like 'updating timestamps'
it 'keeps labels and milestone' do
described_class.new(parent, user, params).execute(issue)
issue.reload
......@@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
it_behaves_like 'updating timestamps'
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
......@@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group|
end
context 'when moving to same list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
let(:assignee) { create(:user) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:issue) do
create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee])
end
it 'returns false' do
expect(described_class.new(parent, user, params).execute(issue)).to eq false
......@@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group|
expect(issue.reload.labels).to contain_exactly(bug, development)
end
it 'sorts issues' do
[issue, issue1, issue2].each do |issue|
issue.move_to_end && issue.save!
end
it 'keeps issues assignees' do
described_class.new(parent, user, params).execute(issue)
expect(issue.reload.assignees).to contain_exactly(assignee)
end
params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
it 'sorts issues' do
reorder_issues(params, issues: [issue, issue1, issue2])
described_class.new(parent, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
it 'does not update updated_at' do
reorder_issues(params, issues: [issue, issue1, issue2])
updated_at = issue.updated_at
updated_at1 = issue1.updated_at
updated_at2 = issue2.updated_at
Timecop.travel(1.minute.from_now) do
described_class.new(parent, user, params).execute(issue)
end
expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0)
expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0)
end
if group
context 'when on a group board' do
it 'sends the board_group_id parameter' do
......@@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group|
end
end
end
def reorder_issues(params, issues: [])
issues.each do |issue|
issue.move_to_end && issue.save!
end
params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id)
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