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 ...@@ -803,8 +803,8 @@ GEM
actionpack (>= 4.0, < 7) actionpack (>= 4.0, < 7)
redis-rack (>= 1, < 3) redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2) redis-store (>= 1.1.0, < 2)
redis-activesupport (5.0.7) redis-activesupport (5.2.0)
activesupport (>= 3, < 6) activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.6.0) redis-namespace (1.6.0)
redis (>= 3.0.4) redis (>= 3.0.4)
...@@ -815,8 +815,8 @@ GEM ...@@ -815,8 +815,8 @@ GEM
redis-actionpack (>= 5.0, < 6) redis-actionpack (>= 5.0, < 6)
redis-activesupport (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.6.0) redis-store (1.8.1)
redis (>= 2.2, < 5) redis (>= 4, < 5)
regexp_parser (1.5.1) regexp_parser (1.5.1)
regexp_property_values (0.3.4) regexp_property_values (0.3.4)
representable (3.0.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> <script>
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import createFlash from '~/flash';
import { sprintf, __ } from '../../../locale'; import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref'; import getRefMixin from '../../mixins/get_ref';
import getFiles from '../../queries/getFiles.query.graphql';
import getProjectPath from '../../queries/getProjectPath.query.graphql'; import getProjectPath from '../../queries/getProjectPath.query.graphql';
import TableHeader from './header.vue'; import TableHeader from './header.vue';
import TableRow from './row.vue'; import TableRow from './row.vue';
import ParentRow from './parent_row.vue'; import ParentRow from './parent_row.vue';
const PAGE_SIZE = 100;
export default { export default {
components: { components: {
GlSkeletonLoading, GlSkeletonLoading,
...@@ -29,22 +25,24 @@ export default { ...@@ -29,22 +25,24 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
entries: {
type: Object,
required: false,
default: () => ({}),
},
isLoading: {
type: Boolean,
required: true,
},
}, },
data() { data() {
return { return {
projectPath: '', projectPath: '',
nextPageCursor: '',
entries: {
trees: [],
submodules: [],
blobs: [],
},
isLoadingFiles: false,
}; };
}, },
computed: { computed: {
tableCaption() { tableCaption() {
if (this.isLoadingFiles) { if (this.isLoading) {
return sprintf( return sprintf(
__( __(
'Loading files, directories, and submodules in the path %{path} for commit reference %{ref}', 'Loading files, directories, and submodules in the path %{path} for commit reference %{ref}',
...@@ -59,65 +57,7 @@ export default { ...@@ -59,65 +57,7 @@ export default {
); );
}, },
showParentRow() { showParentRow() {
return !this.isLoadingFiles && ['', '/'].indexOf(this.path) === -1; return !this.isLoading && ['', '/'].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);
}, },
}, },
}; };
...@@ -145,7 +85,7 @@ export default { ...@@ -145,7 +85,7 @@ export default {
:lfs-oid="entry.lfsOid" :lfs-oid="entry.lfsOid"
/> />
</template> </template>
<template v-if="isLoadingFiles"> <template v-if="isLoading">
<tr v-for="i in 5" :key="i" aria-hidden="true"> <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>
<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 Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import axios from '~/lib/utils/axios_utils';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json'; import introspectionQueryResultData from './fragmentTypes.json';
import { fetchLogsTree } from './log_tree'; import { fetchLogsTree } from './log_tree';
...@@ -27,6 +28,11 @@ const defaultClient = createDefaultClient( ...@@ -27,6 +28,11 @@ const defaultClient = createDefaultClient(
}); });
}); });
}, },
readme(_, { url }) {
return axios
.get(url, { params: { viewer: 'rich', format: 'json' } })
.then(({ data }) => ({ ...data, __typename: 'ReadmeFile' }));
},
}, },
}, },
{ {
......
<script> <script>
import FileTable from '../components/table/index.vue'; import TreeContent from '../components/tree_content.vue';
export default { export default {
components: { components: {
FileTable, TreeContent,
},
data() {
return {
ref: '',
};
}, },
}; };
</script> </script>
<template> <template>
<file-table path="/" /> <tree-content />
</template> </template>
<script> <script>
import FileTable from '../components/table/index.vue'; import TreeContent from '../components/tree_content.vue';
export default { export default {
components: { components: {
FileTable, TreeContent,
}, },
props: { props: {
path: { path: {
...@@ -16,5 +16,5 @@ export default { ...@@ -16,5 +16,5 @@ export default {
</script> </script>
<template> <template>
<file-table :path="path" /> <tree-content :path="path" />
</template> </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 @@ ...@@ -4,7 +4,12 @@
} }
.snippet-filename { .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 ...@@ -17,14 +17,14 @@ class ApplicationController < ActionController::Base
include Gitlab::Tracking::ControllerConcern include Gitlab::Tracking::ControllerConcern
include Gitlab::Experimentation::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 :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket! 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 :ldap_security_check
before_action :sentry_context before_action :sentry_context
before_action :default_headers 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 :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
before_action :active_user_check, unless: :devise_controller? before_action :active_user_check, unless: :devise_controller?
...@@ -95,13 +95,11 @@ class ApplicationController < ActionController::Base ...@@ -95,13 +95,11 @@ class ApplicationController < ActionController::Base
end end
def route_not_found def route_not_found
if current_user # We need to call #authenticate_user! here because sometimes this is called from another action
not_found # and not from our wildcard fallback route
else authenticate_user!
store_location_for(:user, request.fullpath) unless request.xhr?
redirect_to new_user_session_path, alert: I18n.t('devise.failure.unauthenticated') not_found
end
end end
def render(*args) def render(*args)
...@@ -451,8 +449,8 @@ class ApplicationController < ActionController::Base ...@@ -451,8 +449,8 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab')) response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end end
def peek_request? def html_request?
request.path.start_with?('/-/peek') request.format.html?
end end
def json_request? def json_request?
...@@ -462,7 +460,7 @@ class ApplicationController < ActionController::Base ...@@ -462,7 +460,7 @@ class ApplicationController < ActionController::Base
def should_enforce_terms? def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
!(peek_request? || devise_controller?) html_request? && !devise_controller?
end end
def set_usage_stats_consent_flag def set_usage_stats_consent_flag
......
...@@ -4,15 +4,18 @@ module ConfirmEmailWarning ...@@ -4,15 +4,18 @@ module ConfirmEmailWarning
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) } before_action :set_confirm_warning, if: :show_confirm_warning?
end end
protected protected
def show_confirm_warning?
html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
end
def set_confirm_warning def set_confirm_warning
return unless current_user return unless current_user
return if current_user.confirmed? return if current_user.confirmed?
return if peek_request? || json_request? || !request.get?
email = current_user.unconfirmed_email || current_user.email email = current_user.unconfirmed_email || current_user.email
......
# frozen_string_literal: true # frozen_string_literal: true
module UploadsActions module UploadsActions
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include SendFileUpload include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze 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 def create
uploader = UploadService.new(model, params[:file], uploader_class).execute uploader = UploadService.new(model, params[:file], uploader_class).execute
...@@ -64,6 +69,18 @@ module UploadsActions ...@@ -64,6 +69,18 @@ module UploadsActions
private 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 def uploader_class
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -20,7 +20,7 @@ class UploadsController < ApplicationController ...@@ -20,7 +20,7 @@ class UploadsController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :upload_mount_satisfied? before_action :upload_mount_satisfied?
before_action :find_model before_action :model
before_action :authorize_access!, only: [:show] before_action :authorize_access!, only: [:show]
before_action :authorize_create_access!, only: [:create, :authorize] before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize] before_action :verify_workhorse_api!, only: [:authorize]
......
...@@ -137,6 +137,26 @@ module Issuable ...@@ -137,6 +137,26 @@ module Issuable
strip_attributes :title 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 # We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled? def locking_enabled?
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.form-group .form-group
= label_tag :manifest, class: 'label-bold' do = label_tag :manifest, class: 'label-bold' do
= _('Manifest') = _('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 .form-text.text-muted
= _('Import multiple repositories by uploading a manifest file.') = _('Import multiple repositories by uploading a manifest file.')
= link_to icon('question-circle'), help_page_path('user/project/import/manifest') = link_to icon('question-circle'), help_page_path('user/project/import/manifest')
......
...@@ -23,7 +23,5 @@ ...@@ -23,7 +23,5 @@
- if can_edit_tree? - 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/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' = render 'projects/blob/new_dir'
- if @tree.readme
= render "projects/tree/readme", readme: @tree.readme
- else - else
= render 'projects/tree/tree_content', tree: @tree, content_url: content_url = render 'projects/tree/tree_content', tree: @tree, content_url: content_url
...@@ -7,8 +7,9 @@ ...@@ -7,8 +7,9 @@
.title .title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
= snippet.title = snippet.title
- if snippet.file_name - if snippet.file_name.present?
%span.snippet-filename.monospace.d-none.d-sm-inline-block %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 = snippet.file_name
%ul.controls %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', ...@@ -52,7 +52,7 @@ scope(path: '*namespace_id/:project_id',
# /info/refs?service=git-receive-pack, but nothing else. # /info/refs?service=git-receive-pack, but nothing else.
# #
git_http_handshake = lambda do |request| 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.blank? ||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)) request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
end end
......
...@@ -245,12 +245,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -245,12 +245,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
post :validate_query, on: :collection post :validate_query, on: :collection
end end
end end
Gitlab.ee do
resources :alerts, constraints: { id: /\d+/ }, only: [:index, :create, :show, :update, :destroy] do
post :notify, on: :collection
end
end
end end
resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], constraints: { id: /\d+/ } do resources :merge_requests, concerns: :awardable, except: [:new, :create, :show], constraints: { id: /\d+/ } do
...@@ -353,17 +347,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -353,17 +347,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
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] resource :variables, only: [:show, :update]
resources :triggers, only: [:index, :create, :edit, :update, :destroy] resources :triggers, only: [:index, :create, :edit, :update, :destroy]
...@@ -397,11 +380,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -397,11 +380,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :failures get :failures
get :status get :status
get :test_report get :test_report
Gitlab.ee do
get :security
get :licenses
end
end end
member do member do
...@@ -536,24 +514,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -536,24 +514,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :realtime_changes get :realtime_changes
post :create_merge_request post :create_merge_request
get :discussions, format: :json get :discussions, format: :json
Gitlab.ee do
get 'designs(/*vueroute)', to: 'issues#designs', as: :designs, format: false
end
end end
collection do collection do
post :bulk_update post :bulk_update
post :import_csv 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
end end
...@@ -629,6 +594,15 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -629,6 +594,15 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
Gitlab.ee do Gitlab.ee do
resources :managed_licenses, only: [:index, :show, :new, :create, :edit, :update, :destroy] resources :managed_licenses, only: [:index, :show, :new, :create, :edit, :update, :destroy]
end 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 end
resources(:projects, resources(:projects,
...@@ -653,22 +627,4 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -653,22 +627,4 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end 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 end
...@@ -2,17 +2,12 @@ ...@@ -2,17 +2,12 @@
module Constraints module Constraints
class ProjectUrlConstrainer class ProjectUrlConstrainer
def matches?(request, existence_check: true) def matches?(request)
namespace_path = request.params[:namespace_id] namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id] project_path = request.params[:project_id] || request.params[:id]
full_path = [namespace_path, project_path].join('/') full_path = [namespace_path, project_path].join('/')
return false unless ProjectPathValidator.valid_path?(full_path) 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?
end end
end end
end end
...@@ -9,16 +9,17 @@ container_scanning: ...@@ -9,16 +9,17 @@ container_scanning:
name: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION name: registry.gitlab.com/gitlab-org/security-products/analyzers/klar:$CS_MAJOR_VERSION
entrypoint: [] entrypoint: []
variables: variables:
# By default, use the latest clair vulnerabilities database, however, allow it to be overridden here # By default, use the latest clair vulnerabilities database, however, allow it to be overridden here with a specific image
# with a specific version to provide consistency for integration testing purposes # 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_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. CLAIR_DB_IMAGE: "arminc/clair-db:$CLAIR_DB_IMAGE_TAG"
# See https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html#overriding-the-container-scanning-template # 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 # for details
GIT_STRATEGY: none GIT_STRATEGY: none
allow_failure: true allow_failure: true
services: services:
- name: arminc/clair-db:$CLAIR_DB_IMAGE_TAG - name: $CLAIR_DB_IMAGE
alias: clair-vulnerabilities-db alias: clair-vulnerabilities-db
script: script:
# the kubernetes executor currently ignores the Docker image entrypoint value, so the start.sh script must # the kubernetes executor currently ignores the Docker image entrypoint value, so the start.sh script must
......
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
RoutesNotFound = Class.new(StandardError) RoutesNotFound = Class.new(StandardError)
def draw(routes_name) 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}")) drawn_any || raise(RoutesNotFound.new("Cannot find #{routes_name}"))
end end
......
...@@ -90,14 +90,6 @@ describe ApplicationController do ...@@ -90,14 +90,6 @@ describe ApplicationController do
let(:format) { :html } let(:format) { :html }
it_behaves_like 'setting gon variables' 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 end
context 'with json format' do context 'with json format' do
...@@ -105,6 +97,12 @@ describe ApplicationController do ...@@ -105,6 +97,12 @@ describe ApplicationController do
it_behaves_like 'not setting gon variables' it_behaves_like 'not setting gon variables'
end end
context 'with atom format' do
let(:format) { :atom }
it_behaves_like 'not setting gon variables'
end
end end
describe 'session expiration' do describe 'session expiration' do
...@@ -186,7 +184,7 @@ describe ApplicationController do ...@@ -186,7 +184,7 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end end
it 'redirects to login page if not authenticated' do it 'redirects to login page via authenticate_user! if not authenticated' do
get :index get :index
expect(response).to redirect_to new_user_session_path expect(response).to redirect_to new_user_session_path
......
...@@ -142,7 +142,7 @@ describe Projects::CommitsController do ...@@ -142,7 +142,7 @@ describe Projects::CommitsController do
context 'token authentication' do context 'token authentication' do
context 'public project' 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 before do
public_project = create(:project, :repository, :public) public_project = create(:project, :repository, :public)
...@@ -152,7 +152,7 @@ describe Projects::CommitsController do ...@@ -152,7 +152,7 @@ describe Projects::CommitsController do
end end
context 'private project' do 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 before do
private_project = create(:project, :repository, :private) private_project = create(:project, :repository, :private)
private_project.add_maintainer(user) private_project.add_maintainer(user)
......
...@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do ...@@ -146,7 +146,7 @@ describe Projects::ErrorTrackingController do
it 'redirects to sign-in page' do it 'redirects to sign-in page' do
post :list_projects, params: list_projects_params 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
end end
......
...@@ -1441,7 +1441,7 @@ describe Projects::IssuesController do ...@@ -1441,7 +1441,7 @@ describe Projects::IssuesController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :private) } 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 before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
...@@ -1449,7 +1449,7 @@ describe Projects::IssuesController do ...@@ -1449,7 +1449,7 @@ describe Projects::IssuesController do
end end
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 before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
...@@ -111,8 +111,8 @@ describe Projects::ReleasesController do ...@@ -111,8 +111,8 @@ describe Projects::ReleasesController do
context 'when the project is private and the user is not logged in' do context 'when the project is private and the user is not logged in' do
let(:project) { private_project } let(:project) { private_project }
it 'returns a redirect' do it 'returns a 401' do
expect(response).to have_gitlab_http_status(:redirect) expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
end end
......
...@@ -41,7 +41,7 @@ describe Projects::TagsController do ...@@ -41,7 +41,7 @@ describe Projects::TagsController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) } 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 before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace) default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
......
...@@ -1149,7 +1149,7 @@ describe ProjectsController do ...@@ -1149,7 +1149,7 @@ describe ProjectsController do
context 'private project with token authentication' do context 'private project with token authentication' do
let(:private_project) { create(:project, :private) } 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 before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace) default_params.merge!(id: private_project, namespace_id: private_project.namespace)
......
...@@ -228,10 +228,10 @@ describe UploadsController do ...@@ -228,10 +228,10 @@ describe UploadsController do
user.block user.block
end 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" } 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
end end
...@@ -320,10 +320,10 @@ describe UploadsController do ...@@ -320,10 +320,10 @@ describe UploadsController do
end end
context "when not signed in" do 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" } 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
end end
...@@ -343,10 +343,10 @@ describe UploadsController do ...@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user) project.add_maintainer(user)
end 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" } 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
end end
...@@ -439,10 +439,10 @@ describe UploadsController do ...@@ -439,10 +439,10 @@ describe UploadsController do
user.block user.block
end 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" } 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
end end
...@@ -526,10 +526,10 @@ describe UploadsController do ...@@ -526,10 +526,10 @@ describe UploadsController do
end end
context "when not signed in" do 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" } 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
end end
...@@ -549,10 +549,10 @@ describe UploadsController do ...@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user) project.add_maintainer(user)
end 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" } 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
end end
......
...@@ -819,10 +819,7 @@ describe 'Pipelines', :js do ...@@ -819,10 +819,7 @@ describe 'Pipelines', :js do
context 'when project is private' do context 'when project is private' do
let(:project) { create(:project, :private, :repository) } let(:project) { create(:project, :private, :repository) }
it 'redirects the user to sign_in and displays the flash alert' do it { expect(page).to have_content 'You need to sign in' }
expect(page).to have_content 'You need to sign in'
expect(page.current_path).to eq("/users/sign_in")
end
end end
end end
......
...@@ -15,7 +15,7 @@ describe 'User views tags', :feature do ...@@ -15,7 +15,7 @@ describe 'User views tags', :feature do
it do it do
visit project_tags_path(project, format: :atom) 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
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 { shallowMount } from '@vue/test-utils';
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import Table from '~/repository/components/table/index.vue'; import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
let vm; let vm;
let $apollo; let $apollo;
function factory(path, data = () => ({})) { const MOCK_BLOBS = [
$apollo = { {
query: jest.fn().mockReturnValue(Promise.resolve({ data: data() })), 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, { vm = shallowMount(Table, {
propsData: { propsData: {
path, path,
isLoading,
entries,
}, },
mocks: { mocks: {
$apollo, $apollo,
...@@ -31,7 +47,7 @@ describe('Repository table component', () => { ...@@ -31,7 +47,7 @@ describe('Repository table component', () => {
${'app/assets'} | ${'master'} ${'app/assets'} | ${'master'}
${'/'} | ${'test'} ${'/'} | ${'test'}
`('renders table caption for $ref in $path', ({ path, ref }) => { `('renders table caption for $ref in $path', ({ path, ref }) => {
factory(path); factory({ path });
vm.setData({ ref }); vm.setData({ ref });
...@@ -41,40 +57,20 @@ describe('Repository table component', () => { ...@@ -41,40 +57,20 @@ describe('Repository table component', () => {
}); });
it('shows loading icon', () => { it('shows loading icon', () => {
factory('/'); factory({ path: '/', isLoading: true });
vm.setData({ isLoadingFiles: true });
expect(vm.find(GlSkeletonLoading).exists()).toBe(true); expect(vm.find(GlSkeletonLoading).exists()).toBe(true);
}); });
describe('normalizeData', () => { it('renders table rows', () => {
it('normalizes edge nodes', () => { factory({
const output = vm.vm.normalizeData('blobs', [{ node: '1' }, { node: '2' }]); path: '/',
entries: {
expect(output).toEqual(['1', '2']); 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); expect(vm.find(TableRow).exists()).toBe(true);
}); expect(vm.findAll(TableRow).length).toBe(2);
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' });
});
}); });
}); });
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 ...@@ -14,42 +14,15 @@ describe Constraints::ProjectUrlConstrainer do
end end
context 'invalid request' do 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 context "project id ending with .git" do
let(:request) { build_request(namespace.full_path, project.path + '.git') } let(:request) { build_request(namespace.full_path, project.path + '.git') }
it { expect(subject.matches?(request)).to be_falsey } it { expect(subject.matches?(request)).to be_falsey }
end end
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 end
def build_request(namespace, project, method = 'GET') def build_request(namespace, project)
double(:request, double(:request, params: { namespace_id: namespace, id: project })
'get?': (method == 'GET'),
params: { namespace_id: namespace, id: project })
end end
end end
...@@ -158,6 +158,7 @@ describe Gitlab::BitbucketImport::Importer do ...@@ -158,6 +158,7 @@ describe Gitlab::BitbucketImport::Importer do
expect { subject.execute }.to change { MergeRequest.count }.by(1) expect { subject.execute }.to change { MergeRequest.count }.by(1)
merge_request = MergeRequest.first merge_request = MergeRequest.first
expect(merge_request.state).to eq('merged')
expect(merge_request.notes.count).to eq(2) expect(merge_request.notes.count).to eq(2)
expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1) expect(merge_request.notes.map(&:discussion_id).uniq.count).to eq(1)
......
...@@ -111,6 +111,34 @@ describe Issuable do ...@@ -111,6 +111,34 @@ describe Issuable do
end end
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 describe '#milestone_available?' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, group: 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 ...@@ -776,10 +776,6 @@ describe 'project routing' do
it 'routes when :template_type is `issue`' 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') 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 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
end end
......
...@@ -34,15 +34,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params| ...@@ -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 context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do it 'does not log the user in' do
# Several instances of where these specs are shared route the request expect(authentication_metrics)
# through ApplicationController#route_not_found which does not involve .to increment(:user_unauthenticated_counter)
# 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
personal_access_token.update(scopes: [:read_user]) personal_access_token.update(scopes: [:read_user])
...@@ -91,15 +84,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params| ...@@ -91,15 +84,8 @@ shared_examples 'authenticates sessionless user' do |path, format, params|
end end
it "doesn't log the user in otherwise", unless: params[:public] do it "doesn't log the user in otherwise", unless: params[:public] do
# Several instances of where these specs are shared route the request expect(authentication_metrics)
# through ApplicationController#route_not_found which does not involve .to increment(:user_unauthenticated_counter)
# 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
get path, params: default_params.merge(private_token: 'token') get path, params: default_params.merge(private_token: 'token')
......
...@@ -39,7 +39,7 @@ shared_examples 'todos actions' do ...@@ -39,7 +39,7 @@ shared_examples 'todos actions' do
post_create post_create
end.to change { user.todos.count }.by(0) 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 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