Commit d8ccc7a0 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 90a06a20
......@@ -803,8 +803,8 @@ GEM
actionpack (>= 4.0, < 7)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.7)
activesupport (>= 3, < 6)
redis-activesupport (5.2.0)
activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis-namespace (1.6.0)
redis (>= 3.0.4)
......@@ -815,8 +815,8 @@ GEM
redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2)
redis-store (1.6.0)
redis (>= 2.2, < 5)
redis-store (1.8.1)
redis (>= 4, < 5)
regexp_parser (1.5.1)
regexp_property_values (0.3.4)
representable (3.0.4)
......
<script>
import { GlLink, GlLoadingIcon } from '@gitlab/ui';
import getReadmeQuery from '../../queries/getReadme.query.graphql';
export default {
apollo: {
readme: {
query: getReadmeQuery,
variables() {
return {
url: this.blob.webUrl,
};
},
loadingKey: 'loading',
},
},
components: {
GlLink,
GlLoadingIcon,
},
props: {
blob: {
type: Object,
required: true,
},
},
data() {
return {
readme: null,
loading: 0,
};
},
};
</script>
<template>
<article class="file-holder js-hide-on-navigation limited-width-container readme-holder">
<div class="file-title">
<i aria-hidden="true" class="fa fa-file-text-o fa-fw"></i>
<gl-link :href="blob.webUrl">
<strong>{{ blob.name }}</strong>
</gl-link>
</div>
<div class="blob-viewer">
<gl-loading-icon v-if="loading > 0" size="md" class="my-4 mx-auto" />
<div v-else-if="readme" v-html="readme.html"></div>
</div>
</article>
</template>
<script>
import { GlSkeletonLoading } from '@gitlab/ui';
import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
import getFiles from '../../queries/getFiles.query.graphql';
import getProjectPath from '../../queries/getProjectPath.query.graphql';
import TableHeader from './header.vue';
import TableRow from './row.vue';
import ParentRow from './parent_row.vue';
const PAGE_SIZE = 100;
export default {
components: {
GlSkeletonLoading,
......@@ -29,22 +25,24 @@ export default {
type: String,
required: true,
},
entries: {
type: Object,
required: false,
default: () => ({}),
},
isLoading: {
type: Boolean,
required: true,
},
},
data() {
return {
projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
};
},
computed: {
tableCaption() {
if (this.isLoadingFiles) {
if (this.isLoading) {
return sprintf(
__(
'Loading files, directories, and submodules in the path %{path} for commit reference %{ref}',
......@@ -59,65 +57,7 @@ export default {
);
},
showParentRow() {
return !this.isLoadingFiles && ['', '/'].indexOf(this.path) === -1;
},
},
watch: {
$route: function routeChange() {
this.entries.trees = [];
this.entries.submodules = [];
this.entries.blobs = [];
this.nextPageCursor = '';
this.fetchFiles();
},
},
mounted() {
// We need to wait for `ref` and `projectPath` to be set
this.$nextTick(() => this.fetchFiles());
},
methods: {
fetchFiles() {
this.isLoadingFiles = true;
return this.$apollo
.query({
query: getFiles,
variables: {
projectPath: this.projectPath,
ref: this.ref,
path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
})
.then(({ data }) => {
if (!data) return;
const pageInfo = this.hasNextPage(data.project.repository.tree);
this.isLoadingFiles = false;
this.entries = Object.keys(this.entries).reduce(
(acc, key) => ({
...acc,
[key]: this.normalizeData(key, data.project.repository.tree[key].edges),
}),
{},
);
if (pageInfo && pageInfo.hasNextPage) {
this.nextPageCursor = pageInfo.endCursor;
this.fetchFiles();
}
})
.catch(() => createFlash(__('An error occurred while fetching folder content.')));
},
normalizeData(key, data) {
return this.entries[key].concat(data.map(({ node }) => node));
},
hasNextPage(data) {
return []
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage);
return !this.isLoading && ['', '/'].indexOf(this.path) === -1;
},
},
};
......@@ -145,7 +85,7 @@ export default {
:lfs-oid="entry.lfsOid"
/>
</template>
<template v-if="isLoadingFiles">
<template v-if="isLoading">
<tr v-for="i in 5" :key="i" aria-hidden="true">
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
......
<script>
import createFlash from '~/flash';
import { __ } from '../../locale';
import FileTable from './table/index.vue';
import getRefMixin from '../mixins/get_ref';
import getFiles from '../queries/getFiles.query.graphql';
import getProjectPath from '../queries/getProjectPath.query.graphql';
import FilePreview from './preview/index.vue';
import { readmeFile } from '../utils/readme';
const PAGE_SIZE = 100;
export default {
components: {
FileTable,
FilePreview,
},
mixins: [getRefMixin],
apollo: {
projectPath: {
query: getProjectPath,
},
},
props: {
path: {
type: String,
required: false,
default: '/',
},
},
data() {
return {
projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
};
},
computed: {
readme() {
return readmeFile(this.entries.blobs);
},
},
watch: {
$route: function routeChange() {
this.entries.trees = [];
this.entries.submodules = [];
this.entries.blobs = [];
this.nextPageCursor = '';
this.fetchFiles();
},
},
mounted() {
// We need to wait for `ref` and `projectPath` to be set
this.$nextTick(() => this.fetchFiles());
},
methods: {
fetchFiles() {
this.isLoadingFiles = true;
return this.$apollo
.query({
query: getFiles,
variables: {
projectPath: this.projectPath,
ref: this.ref,
path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
})
.then(({ data }) => {
if (!data) return;
const pageInfo = this.hasNextPage(data.project.repository.tree);
this.isLoadingFiles = false;
this.entries = Object.keys(this.entries).reduce(
(acc, key) => ({
...acc,
[key]: this.normalizeData(key, data.project.repository.tree[key].edges),
}),
{},
);
if (pageInfo && pageInfo.hasNextPage) {
this.nextPageCursor = pageInfo.endCursor;
this.fetchFiles();
}
})
.catch(() => createFlash(__('An error occurred while fetching folder content.')));
},
normalizeData(key, data) {
return this.entries[key].concat(data.map(({ node }) => node));
},
hasNextPage(data) {
return []
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage);
},
},
};
</script>
<template>
<div>
<file-table :path="path" :entries="entries" :is-loading="isLoadingFiles" />
<file-preview v-if="readme" :blob="readme" />
</div>
</template>
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
import { fetchLogsTree } from './log_tree';
......@@ -27,6 +28,11 @@ const defaultClient = createDefaultClient(
});
});
},
readme(_, { url }) {
return axios
.get(url, { params: { viewer: 'rich', format: 'json' } })
.then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
},
},
},
{
......
<script>
import FileTable from '../components/table/index.vue';
import TreeContent from '../components/tree_content.vue';
export default {
components: {
FileTable,
},
data() {
return {
ref: '',
};
TreeContent,
},
};
</script>
<template>
<file-table path="/" />
<tree-content />
</template>
<script>
import FileTable from '../components/table/index.vue';
import TreeContent from '../components/tree_content.vue';
export default {
components: {
FileTable,
TreeContent,
},
props: {
path: {
......@@ -16,5 +16,5 @@ export default {
</script>
<template>
<file-table :path="path" />
<tree-content :path="path" />
</template>
query getReadme($url: String!) {
readme(url: $url) @client {
html
}
}
const MARKDOWN_EXTENSIONS = ['mdown', 'mkd', 'mkdn', 'md', 'markdown'];
const ASCIIDOC_EXTENSIONS = ['adoc', 'ad', 'asciidoc'];
const OTHER_EXTENSIONS = ['textile', 'rdoc', 'org', 'creole', 'wiki', 'mediawiki', 'rst'];
const EXTENSIONS = [...MARKDOWN_EXTENSIONS, ...ASCIIDOC_EXTENSIONS, ...OTHER_EXTENSIONS];
const PLAIN_FILENAMES = ['readme', 'index'];
const FILE_REGEXP = new RegExp(`^(${PLAIN_FILENAMES.join('|')})`, 'i');
const EXTENSIONS_REGEXP = new RegExp(`.(${EXTENSIONS.join('|')})$`, 'i');
// eslint-disable-next-line import/prefer-default-export
export const readmeFile = blobs => {
const readMeFiles = blobs.filter(f => f.name.search(FILE_REGEXP) !== -1);
const previewableReadme = readMeFiles.find(f => f.name.search(EXTENSIONS_REGEXP) !== -1);
const plainReadme = readMeFiles.find(f => f.name.search(FILE_REGEXP) !== -1);
return previewableReadme || plainReadme;
};
......@@ -4,7 +4,12 @@
}
.snippet-filename {
padding: 0 2px;
color: $gl-text-color-secondary;
font-weight: normal;
}
.snippet-info {
color: $gl-text-color-secondary;
}
}
......
......@@ -17,14 +17,14 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::ControllerConcern
before_action :authenticate_user!, except: [:route_not_found]
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
before_action :check_password_expiration
before_action :check_password_expiration, if: :html_request?
before_action :ldap_security_check
before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables, unless: [:peek_request?, :json_request?]
before_action :add_gon_variables, if: :html_request?
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
before_action :active_user_check, unless: :devise_controller?
......@@ -95,13 +95,11 @@ class ApplicationController < ActionController::Base
end
def route_not_found
if current_user
not_found
else
store_location_for(:user, request.fullpath) unless request.xhr?
# We need to call #authenticate_user! here because sometimes this is called from another action
# and not from our wildcard fallback route
authenticate_user!
redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated')
end
not_found
end
def render(*args)
......@@ -451,8 +449,8 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end
def peek_request?
request.path.start_with?('/-/peek')
def html_request?
request.format.html?
end
def json_request?
......@@ -462,7 +460,7 @@ class ApplicationController < ActionController::Base
def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
!(peek_request? || devise_controller?)
html_request? && !devise_controller?
end
def set_usage_stats_consent_flag
......
......@@ -4,15 +4,18 @@ module ConfirmEmailWarning
extend ActiveSupport::Concern
included do
before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) }
before_action :set_confirm_warning, if: :show_confirm_warning?
end
protected
def show_confirm_warning?
html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
end
def set_confirm_warning
return unless current_user
return if current_user.confirmed?
return if peek_request? || json_request? || !request.get?
email = current_user.unconfirmed_email || current_user.email
......
# frozen_string_literal: true
module UploadsActions
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
included do
prepend_before_action :set_request_format_from_path_extension
end
def create
uploader = UploadService.new(model, params[:file], uploader_class).execute
......@@ -64,6 +69,18 @@ module UploadsActions
private
# From ActionDispatch::Http::MimeNegotiation. We have an initializer that
# monkey-patches this method out (so that repository paths don't guess a
# format based on extension), but we do want this behaviour when serving
# uploads.
def set_request_format_from_path_extension
path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
if match = path&.match(/\.(\w+)\z/)
request.format = match.captures.first
end
end
def uploader_class
raise NotImplementedError
end
......
......@@ -20,7 +20,7 @@ class UploadsController < ApplicationController
skip_before_action :authenticate_user!
before_action :upload_mount_satisfied?
before_action :find_model
before_action :model
before_action :authorize_access!, only: [:show]
before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
......
......@@ -137,6 +137,26 @@ module Issuable
strip_attributes :title
# The state_machine gem will reset the value of state_id unless it
# is a raw attribute passed in here:
# https://gitlab.com/gitlab-org/gitlab/issues/35746#note_241148787
#
# This assumes another initialize isn't defined. Otherwise this
# method may need to be prepended.
def initialize(attributes = nil)
if attributes.is_a?(Hash)
attr = attributes.symbolize_keys
if attr.key?(:state) && !attr.key?(:state_id)
value = attr.delete(:state)
state_id = self.class.available_states[value]
attributes[:state_id] = state_id if state_id
end
end
super(attributes)
end
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
......
......@@ -13,7 +13,7 @@
.form-group
= label_tag :manifest, class: 'label-bold' do
= _('Manifest')
= file_field_tag :manifest, class: 'form-control-file', required: true
= file_field_tag :manifest, class: 'form-control-file w-auto', required: true
.form-text.text-muted
= _('Import multiple repositories by uploading a manifest file.')
= link_to icon('question-circle'), help_page_path('user/project/import/manifest')
......
......@@ -23,7 +23,5 @@
- if can_edit_tree?
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
- if @tree.readme
= render "projects/tree/readme", readme: @tree.readme
- else
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url
......@@ -7,8 +7,9 @@
.title
= link_to reliable_snippet_path(snippet) do
= snippet.title
- if snippet.file_name
%span.snippet-filename.monospace.d-none.d-sm-inline-block
- if snippet.file_name.present?
%span.snippet-filename.d-none.d-sm-inline-block.ml-2
= sprite_icon('doc-code', size: 16, css_class: 'file-icon align-text-bottom')
= snippet.file_name
%ul.controls
......
---
title: Add max width on manifest file attachment input
merge_request: 19028
author:
type: fixed
---
title: Make snippet list easier to scan
merge_request: 19490
author:
type: other
---
title: Fix JSON responses returning 302 instead of 401
merge_request: 19412
author:
type: fixed
---
title: Allow container scanning to run offline by specifying the Clair DB image to use.
merge_request: 19161
author:
type: changed
---
title: Fix Bitbucket Cloud importer pull request state
merge_request: 19734
author:
type: fixed
......@@ -52,7 +52,7 @@ scope(path: '*namespace_id/:project_id',
# /info/refs?service=git-receive-pack, but nothing else.
#
git_http_handshake = lambda do |request|
::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
(request.query_string.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
end
......
......@@ -245,12 +245,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :validate_query, on: :collection
end
end
Gitlab.ee do
resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
post :notify, on: :collection
end
end
end
resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], constraints: { id: /\d+/ } do
......@@ -353,17 +347,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
Gitlab.ee do
resources :path_locks, only: [:index, :destroy] do
collection do
post :toggle
end
end
get '/service_desk' => 'service_desk#show', as: :service_desk
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
end
resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :edit, :update, :destroy]
......@@ -397,11 +380,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :failures
get :status
get :test_report
Gitlab.ee do
get :security
get :licenses
end
end
member do
......@@ -536,24 +514,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :realtime_changes
post :create_merge_request
get :discussions, format: :json
Gitlab.ee do
get 'designs(/*vueroute)', to: 'issues#designs', as: :designs, format: false
end
end
collection do
post :bulk_update
post :import_csv
Gitlab.ee do
post :export_csv
get :service_desk
end
end
Gitlab.ee do
resources :issue_links, only: [:index, :create, :destroy], as: 'links', path: 'links'
end
end
......@@ -629,6 +594,15 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
Gitlab.ee do
resources :managed_licenses, only: [:index, :show, :new, :create, :edit, :update, :destroy]
end
# Legacy routes.
# Introduced in 12.0.
# Should be removed after 12.1
Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
:network, :graphs, :autocomplete_sources,
:project_members, :deploy_keys, :deploy_tokens,
:labels, :milestones, :services, :boards, :releases,
:forks, :group_links, :import, :avatar)
end
resources(:projects,
......@@ -653,22 +627,4 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
end
# Legacy routes.
# Introduced in 12.0.
# Should be removed after 12.1
scope(path: '*namespace_id',
as: :namespace,
namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
scope(path: ':project_id',
constraints: { project_id: Gitlab::PathRegex.project_route_regex },
module: :projects,
as: :project) do
Gitlab::Routing.redirect_legacy_paths(self, :settings, :branches, :tags,
:network, :graphs, :autocomplete_sources,
:project_members, :deploy_keys, :deploy_tokens,
:labels, :milestones, :services, :boards, :releases,
:forks, :group_links, :import, :avatar)
end
end
end
......@@ -2,17 +2,12 @@
module Constraints
class ProjectUrlConstrainer
def matches?(request, existence_check: true)
def matches?(request)
namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id]
full_path = [namespace_path, project_path].join('/')
return false unless ProjectPathValidator.valid_path?(full_path)
return true unless existence_check
# We intentionally allow SELECT(*) here so result of this query can be used
# as cache for further Project.find_by_full_path calls within request
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
ProjectPathValidator.valid_path?(full_path)
end
end
end
......@@ -9,16 +9,17 @@ container_scanning:
name: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION
entrypoint: []
variables:
# By default, use the latest clair vulnerabilities database, however, allow it to be overridden here
# with a specific version to provide consistency for integration testing purposes
CLAIR_DB_IMAGE_TAG: latest
# Override this variable in your `.gitlab-ci.yml` file and set it to `fetch` if you want to provide a `clair-whitelist.yaml` file.
# See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
# By default, use the latest clair vulnerabilities database, however, allow it to be overridden here with a specific image
# to enable container scanning to run offline, or to provide a consistent list of vulnerabilities for integration testing purposes
CLAIR_DB_IMAGE_TAG: "latest"
CLAIR_DB_IMAGE: "arminc/clair-db:$CLAIR_DB_IMAGE_TAG"
# Override the GIT_STRATEGY variable in your `.gitlab-ci.yml` file and set it to `fetch` if you want to provide a `clair-whitelist.yml`
# file. See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template
# for details
GIT_STRATEGY: none
allow_failure: true
services:
- name: arminc/clair-db:$CLAIR_DB_IMAGE_TAG
- name: $CLAIR_DB_IMAGE
alias: clair-vulnerabilities-db
script:
# the kubernetes executor currently ignores the Docker image entrypoint value, so the start.sh script must
......
......@@ -10,7 +10,7 @@ module Gitlab
RoutesNotFound = Class.new(StandardError)
def draw(routes_name)
drawn_any = draw_ce(routes_name) | draw_ee(routes_name)
drawn_any = draw_ee(routes_name) | draw_ce(routes_name)
drawn_any || raise(RoutesNotFound.new("Cannot find #{routes_name}"))
end
......
......@@ -90,14 +90,6 @@ describe ApplicationController do
let(:format) { :html }
it_behaves_like 'setting gon variables'
context 'for peek requests' do
before do
request.path = '/-/peek'
end
it_behaves_like 'not setting gon variables'
end
end
context 'with json format' do
......@@ -105,6 +97,12 @@ describe ApplicationController do
it_behaves_like 'not setting gon variables'
end
context 'with atom format' do
let(:format) { :atom }
it_behaves_like 'not setting gon variables'
end
end
describe 'session expiration' do
......@@ -186,7 +184,7 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(404)
end
it 'redirects to login page if not authenticated' do
it 'redirects to login page via authenticate_user! if not authenticated' do
get :index
expect(response).to redirect_to new_user_session_path
......
......@@ -142,7 +142,7 @@ describe Projects::CommitsController do
context 'token authentication' do
context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
public_project = create(:project, :repository, :public)
......@@ -152,7 +152,7 @@ describe Projects::CommitsController do
end
context 'private project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
......
......@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do
it 'redirects to sign-in page' do
post :list_projects, params: list_projects_params
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
......
......@@ -1441,7 +1441,7 @@ describe Projects::IssuesController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......@@ -1449,7 +1449,7 @@ describe Projects::IssuesController do
end
end
it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
it_behaves_like 'authenticates sessionless user', :calendar, :ics do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
......@@ -111,8 +111,8 @@ describe Projects::ReleasesController do
context 'when the project is private and the user is not logged in' do
let(:project) { private_project }
it 'returns a redirect' do
expect(response).to have_gitlab_http_status(:redirect)
it 'returns a 401' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
......
......@@ -41,7 +41,7 @@ describe Projects::TagsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
......@@ -1149,7 +1149,7 @@ describe ProjectsController do
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
it_behaves_like 'authenticates sessionless user', :show, :atom do
before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace)
......
......@@ -228,10 +228,10 @@ describe UploadsController do
user.block
end
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......@@ -320,10 +320,10 @@ describe UploadsController do
end
context "when not signed in" do
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user)
end
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......@@ -439,10 +439,10 @@ describe UploadsController do
user.block
end
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......@@ -526,10 +526,10 @@ describe UploadsController do
end
context "when not signed in" do
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user)
end
it "redirects to the sign in page" do
it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path)
expect(response).to have_gitlab_http_status(401)
end
end
......
......@@ -819,10 +819,7 @@ describe 'Pipelines', :js do
context 'when project is private' do
let(:project) { create(:project, :private, :repository) }
it 'redirects the user to sign_in and displays the flash alert' do
expect(page).to have_content 'You need to sign in'
expect(page.current_path).to eq("/users/sign_in")
end
it { expect(page).to have_content 'You need to sign in' }
end
end
......
......@@ -15,7 +15,7 @@ describe 'User views tags', :feature do
it do
visit project_tags_path(project, format: :atom)
expect(page.current_path).to eq("/users/sign_in")
expect(page).to have_gitlab_http_status(401)
end
end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Repository file preview component renders file HTML 1`] = `
<article
class="file-holder js-hide-on-navigation limited-width-container readme-holder"
>
<div
class="file-title"
>
<i
aria-hidden="true"
class="fa fa-file-text-o fa-fw"
/>
<gllink-stub
href="http://test.com"
>
<strong>
README.md
</strong>
</gllink-stub>
</div>
<div
class="blob-viewer"
>
<div>
<div
class="blob"
>
test
</div>
</div>
</div>
</article>
`;
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui';
import Preview from '~/repository/components/preview/index.vue';
let vm;
let $apollo;
function factory(blob) {
$apollo = {
query: jest.fn().mockReturnValue(Promise.resolve({})),
};
vm = shallowMount(Preview, {
propsData: {
blob,
},
mocks: {
$apollo,
},
});
}
describe('Repository file preview component', () => {
afterEach(() => {
vm.destroy();
});
it('renders file HTML', () => {
factory({
webUrl: 'http://test.com',
name: 'README.md',
});
vm.setData({ readme: { html: '<div class="blob">test</div>' } });
expect(vm.element).toMatchSnapshot();
});
it('renders loading icon', () => {
factory({
webUrl: 'http://test.com',
name: 'README.md',
});
vm.setData({ loading: 1 });
expect(vm.find(GlLoadingIcon).exists()).toBe(true);
});
});
import { shallowMount } from '@vue/test-utils';
import { GlSkeletonLoading } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
let vm;
let $apollo;
function factory(path, data = () => ({})) {
$apollo = {
query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
};
const MOCK_BLOBS = [
{
id: '123abc',
flatPath: 'blob',
name: 'blob.md',
type: 'blob',
webUrl: 'http://test.com',
},
{
id: '124abc',
flatPath: 'blob2',
name: 'blob2.md',
type: 'blob',
webUrl: 'http://test.com',
},
];
function factory({ path, isLoading = false, entries = {} }) {
vm = shallowMount(Table, {
propsData: {
path,
isLoading,
entries,
},
mocks: {
$apollo,
......@@ -31,7 +47,7 @@ describe('Repository table component', () => {
${'app/assets'} | ${'master'}
${'/'} | ${'test'}
`('renders table caption for $ref in $path', ({ path, ref }) => {
factory(path);
factory({ path });
vm.setData({ ref });
......@@ -41,40 +57,20 @@ describe('Repository table component', () => {
});
it('shows loading icon', () => {
factory('/');
vm.setData({ isLoadingFiles: true });
factory({ path: '/', isLoading: true });
expect(vm.find(GlSkeletonLoading).exists()).toBe(true);
});
describe('normalizeData', () => {
it('normalizes edge nodes', () => {
const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
expect(output).toEqual(['1', '2']);
it('renders table rows', () => {
factory({
path: '/',
entries: {
blobs: MOCK_BLOBS,
},
});
});
describe('hasNextPage', () => {
it('returns undefined when hasNextPage is false', () => {
const output = vm.vm.hasNextPage({
trees: { pageInfo: { hasNextPage: false } },
submodules: { pageInfo: { hasNextPage: false } },
blobs: { pageInfo: { hasNextPage: false } },
});
expect(output).toBe(undefined);
});
it('returns pageInfo object when hasNextPage is true', () => {
const output = vm.vm.hasNextPage({
trees: { pageInfo: { hasNextPage: false } },
submodules: { pageInfo: { hasNextPage: false } },
blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
});
expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
});
expect(vm.find(TableRow).exists()).toBe(true);
expect(vm.findAll(TableRow).length).toBe(2);
});
});
import { shallowMount } from '@vue/test-utils';
import TreeContent from '~/repository/components/tree_content.vue';
import FilePreview from '~/repository/components/preview/index.vue';
let vm;
let $apollo;
function factory(path, data = () => ({})) {
$apollo = {
query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })),
};
vm = shallowMount(TreeContent, {
propsData: {
path,
},
mocks: {
$apollo,
},
});
}
describe('Repository table component', () => {
afterEach(() => {
vm.destroy();
});
it('renders file preview', () => {
factory('/');
vm.setData({ entries: { blobs: [{ name: 'README.md ' }] } });
expect(vm.find(FilePreview).exists()).toBe(true);
});
describe('normalizeData', () => {
it('normalizes edge nodes', () => {
factory('/');
const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]);
expect(output).toEqual(['1', '2']);
});
});
describe('hasNextPage', () => {
it('returns undefined when hasNextPage is false', () => {
factory('/');
const output = vm.vm.hasNextPage({
trees: { pageInfo: { hasNextPage: false } },
submodules: { pageInfo: { hasNextPage: false } },
blobs: { pageInfo: { hasNextPage: false } },
});
expect(output).toBe(undefined);
});
it('returns pageInfo object when hasNextPage is true', () => {
factory('/');
const output = vm.vm.hasNextPage({
trees: { pageInfo: { hasNextPage: false } },
submodules: { pageInfo: { hasNextPage: false } },
blobs: { pageInfo: { hasNextPage: true, nextCursor: 'test' } },
});
expect(output).toEqual({ hasNextPage: true, nextCursor: 'test' });
});
});
});
......@@ -14,42 +14,15 @@ describe Constraints::ProjectUrlConstrainer do
end
context 'invalid request' do
context "non-existing project" do
let(:request) { build_request('foo', 'bar') }
it { expect(subject.matches?(request)).to be_falsey }
context 'existence_check is false' do
it { expect(subject.matches?(request, existence_check: false)).to be_truthy }
end
end
context "project id ending with .git" do
let(:request) { build_request(namespace.full_path, project.path + '.git') }
it { expect(subject.matches?(request)).to be_falsey }
end
end
context 'when the request matches a redirect route' do
let(:old_project_path) { 'old_project_path' }
let!(:redirect_route) { project.redirect_routes.create!(path: "#{namespace.full_path}/#{old_project_path}") }
context 'and is a GET request' do
let(:request) { build_request(namespace.full_path, old_project_path) }
it { expect(subject.matches?(request)).to be_truthy }
end
context 'and is NOT a GET request' do
let(:request) { build_request(namespace.full_path, old_project_path, 'POST') }
it { expect(subject.matches?(request)).to be_falsey }
end
end
end
def build_request(namespace, project, method = 'GET')
double(:request,
'get?': (method == 'GET'),
params: { namespace_id: namespace, id: project })
def build_request(namespace, project)
double(:request, params: { namespace_id: namespace, id: project })
end
end
......@@ -158,6 +158,7 @@ describe Gitlab::BitbucketImport::Importer do
expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first
expect(merge_request.state).to eq('merged')
expect(merge_request.notes.count).to eq(2)
expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
......
......@@ -111,6 +111,34 @@ describe Issuable do
end
end
describe '.initialize' do
it 'maps the state to the right state_id' do
described_class::STATE_ID_MAP.each do |key, value|
issuable = MergeRequest.new(state: key)
expect(issuable.state).to eq(key)
expect(issuable.state_id).to eq(value)
end
end
it 'maps a string version of the state to the right state_id' do
described_class::STATE_ID_MAP.each do |key, value|
issuable = MergeRequest.new('state' => key)
expect(issuable.state).to eq(key)
expect(issuable.state_id).to eq(value)
end
end
it 'gives preference to state_id if present' do
issuable = MergeRequest.new('state' => 'opened',
'state_id' => described_class::STATE_ID_MAP['merged'])
expect(issuable.state).to eq('merged')
expect(issuable.state_id).to eq(described_class::STATE_ID_MAP['merged'])
end
end
describe '#milestone_available?' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Projects::BlobController do
let(:project) { create(:project, :private, :repository) }
let(:namespace) { project.namespace }
context 'anonymous user views blob in inaccessible project' do
context 'with default HTML format' do
before do
get namespace_project_blob_path(namespace_id: namespace, project_id: project, id: 'master/README.md')
end
context 'when project is private' do
it { expect(response).to have_gitlab_http_status(:redirect) }
end
context 'when project does not exist' do
let(:namespace) { 'non_existent_namespace' }
let(:project) { 'non_existent_project' }
it { expect(response).to have_gitlab_http_status(:redirect) }
end
end
context 'with JSON format' do
before do
get namespace_project_blob_path(namespace_id: namespace, project_id: project, id: 'master/README.md', format: :json)
end
context 'when project is private' do
it { expect(response).to have_gitlab_http_status(:unauthorized) }
end
context 'when project does not exist' do
let(:namespace) { 'non_existent_namespace' }
let(:project) { 'non_existent_project' }
it { expect(response).to have_gitlab_http_status(:unauthorized) }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
# The exact query count will vary depending on the 2FA settings of the
# instance, group, and user. Removing those extra 2FA queries in this case
# may not be a good idea, so we just set up the ideal case.
before do
stub_application_setting(require_two_factor_authentication: true)
login_as(create(:user, :two_factor))
end
# One each for: current user, avatar user, and upload record
it 'only performs three SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(3)
end
end
context 'when logged out' do
# One each for avatar user and upload record
it 'only performs two SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(2)
end
end
end
......@@ -776,10 +776,6 @@ describe 'project routing' do
it 'routes when :template_type is `issue`' do
expect(get(show_with_template_type('issue'))).to route_to('projects/templates#show', namespace_id: 'gitlab', project_id: 'gitlabhq', template_type: 'issue', key: 'template_name', format: 'json')
end
it 'routes to application#route_not_found when :template_type is unknown' do
expect(get(show_with_template_type('invalid'))).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/templates/invalid/template_name')
end
end
end
......
......@@ -34,15 +34,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
personal_access_token.update(scopes: [:read_user])
......@@ -91,15 +84,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
end
it "doesn't log the user in otherwise", unless: params[:public] do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get path, params: default_params.merge(private_token: 'token')
......
......@@ -39,7 +39,7 @@ shared_examples 'todos actions' do
post_create
end.to change { user.todos.count }.by(0)
expect(response).to have_gitlab_http_status(302)
expect(response).to have_gitlab_http_status(parent.is_a?(Group) ? 401 : 302)
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