diff options
50 files changed, 655 insertions, 298 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index ecc68463f89..4eac23b7a71 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue new file mode 100644 index 00000000000..564be211c46 --- /dev/null +++ b/app/assets/javascripts/repository/components/preview/index.vue @@ -0,0 +1,49 @@ +<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> diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 98923c79c7a..ac20549acb8 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -1,16 +1,12 @@ <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> diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue new file mode 100644 index 00000000000..949e653fc8f --- /dev/null +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -0,0 +1,115 @@ +<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> diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js index 6cb253c8169..6936c08d852 100644 --- a/app/assets/javascripts/repository/graphql.js +++ b/app/assets/javascripts/repository/graphql.js @@ -1,6 +1,7 @@ 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' })); + }, }, }, { diff --git a/app/assets/javascripts/repository/pages/index.vue b/app/assets/javascripts/repository/pages/index.vue index 2d92e9174ca..967f4a99281 100644 --- a/app/assets/javascripts/repository/pages/index.vue +++ b/app/assets/javascripts/repository/pages/index.vue @@ -1,18 +1,13 @@ <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> diff --git a/app/assets/javascripts/repository/pages/tree.vue b/app/assets/javascripts/repository/pages/tree.vue index 3b898d1aa91..19300099449 100644 --- a/app/assets/javascripts/repository/pages/tree.vue +++ b/app/assets/javascripts/repository/pages/tree.vue @@ -1,9 +1,9 @@ <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> diff --git a/app/assets/javascripts/repository/queries/getReadme.query.graphql b/app/assets/javascripts/repository/queries/getReadme.query.graphql new file mode 100644 index 00000000000..cf056330133 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getReadme.query.graphql @@ -0,0 +1,5 @@ +query getReadme($url: String!) { + readme(url: $url) @client { + html + } +} diff --git a/app/assets/javascripts/repository/utils/readme.js b/app/assets/javascripts/repository/utils/readme.js new file mode 100644 index 00000000000..b219b857c66 --- /dev/null +++ b/app/assets/javascripts/repository/utils/readme.js @@ -0,0 +1,17 @@ +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; +}; diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss index f57b1d9f351..404f60f17ee 100644 --- a/app/assets/stylesheets/framework/snippets.scss +++ b/app/assets/stylesheets/framework/snippets.scss @@ -4,7 +4,12 @@ } .snippet-filename { - padding: 0 2px; + color: $gl-text-color-secondary; + font-weight: normal; + } + + .snippet-info { + color: $gl-text-color-secondary; } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index af2869ddba7..1311c745da3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index 5a4b5897a4f..874deeb4702 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -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 diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb index b87779c22d3..023c41821da 100644 --- a/app/controllers/concerns/uploads_actions.rb +++ b/app/controllers/concerns/uploads_actions.rb @@ -1,11 +1,16 @@ # 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 diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index 635db386792..f39a2b81b54 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -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] diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 852576dbbc2..796e6438a2c 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -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? diff --git a/app/views/import/manifest/_form.html.haml b/app/views/import/manifest/_form.html.haml index 78c7fadb019..b515ce084e4 100644 --- a/app/views/import/manifest/_form.html.haml +++ b/app/views/import/manifest/_form.html.haml @@ -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') diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml index 3c0dfd4c029..6681bb4d094 100644 --- a/app/views/projects/_files.html.haml +++ b/app/views/projects/_files.html.haml @@ -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 diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 0ef626868a2..5602ea37b5c 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -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 diff --git a/changelogs/unreleased/28350-manifest-error-file-attach.yml b/changelogs/unreleased/28350-manifest-error-file-attach.yml new file mode 100644 index 00000000000..f1c11d5f1bd --- /dev/null +++ b/changelogs/unreleased/28350-manifest-error-file-attach.yml @@ -0,0 +1,5 @@ +--- +title: Add max width on manifest file attachment input +merge_request: 19028 +author: +type: fixed diff --git a/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml b/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml new file mode 100644 index 00000000000..d10165438d4 --- /dev/null +++ b/changelogs/unreleased/31964-make-snippet-list-easier-to-scan.yml @@ -0,0 +1,5 @@ +--- +title: Make snippet list easier to scan +merge_request: 19490 +author: +type: other diff --git a/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml b/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml new file mode 100644 index 00000000000..d33803cfdec --- /dev/null +++ b/changelogs/unreleased/35289-remove-existence-check-in-url-constrainer.yml @@ -0,0 +1,5 @@ +--- +title: Fix JSON responses returning 302 instead of 401 +merge_request: 19412 +author: +type: fixed diff --git a/changelogs/unreleased/allow-container-scanning-to-run-offline.yml b/changelogs/unreleased/allow-container-scanning-to-run-offline.yml new file mode 100644 index 00000000000..5cce0565d28 --- /dev/null +++ b/changelogs/unreleased/allow-container-scanning-to-run-offline.yml @@ -0,0 +1,5 @@ +--- +title: Allow container scanning to run offline by specifying the Clair DB image to use. +merge_request: 19161 +author: +type: changed diff --git a/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml b/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml new file mode 100644 index 00000000000..f5b90a296c1 --- /dev/null +++ b/changelogs/unreleased/sh-fix-bitbucket-importer-pr-state.yml @@ -0,0 +1,5 @@ +--- +title: Fix Bitbucket Cloud importer pull request state +merge_request: 19734 +author: +type: fixed diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb index aac6d418a92..2e70fd9f1b6 100644 --- a/config/routes/git_http.rb +++ b/config/routes/git_http.rb @@ -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 diff --git a/config/routes/project.rb b/config/routes/project.rb index d49ba20ce84..62a70b4655e 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -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 diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb index d41490d2ebd..64eefd67d81 100644 --- a/lib/constraints/project_url_constrainer.rb +++ b/lib/constraints/project_url_constrainer.rb @@ -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 diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml index f058468ed8e..ef2fc561201 100644 --- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/patch/draw_route.rb b/lib/gitlab/patch/draw_route.rb index 4c8ca015974..4d1b57fbbbb 100644 --- a/lib/gitlab/patch/draw_route.rb +++ b/lib/gitlab/patch/draw_route.rb @@ -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 diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index bb7f5ec2b28..481883c50fa 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -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 diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 1977e92e42b..9c4d6fdcb2a 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -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) diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index 31868f5f717..4c224e960a6 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -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 diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 8770a5ee303..4c2b58551bf 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -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) diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index 562119d967f..28ca20d7dab 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -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 diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index f077b4c99fc..b99b5d611fc 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -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) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 22538565698..321f5ecdbc9 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -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) diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index 1bcf3bb106b..f35babc1b56 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -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 diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f6eeb8d7065..a9a127da56f 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -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 diff --git a/spec/features/projects/tags/user_views_tags_spec.rb b/spec/features/projects/tags/user_views_tags_spec.rb index bc570f502bf..f344b682715 100644 --- a/spec/features/projects/tags/user_views_tags_spec.rb +++ b/spec/features/projects/tags/user_views_tags_spec.rb @@ -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 diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap new file mode 100644 index 00000000000..3d5ec3fd411 --- /dev/null +++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap @@ -0,0 +1,36 @@ +// 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> +`; diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js new file mode 100644 index 00000000000..0112e6310f4 --- /dev/null +++ b/spec/frontend/repository/components/preview/index_spec.js @@ -0,0 +1,49 @@ +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); + }); +}); diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js index 0c5a7370fca..1e2f3501c8c 100644 --- a/spec/frontend/repository/components/table/index_spec.js +++ b/spec/frontend/repository/components/table/index_spec.js @@ -1,18 +1,34 @@ 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); }); }); diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js new file mode 100644 index 00000000000..954c4791c04 --- /dev/null +++ b/spec/frontend/repository/components/tree_content_spec.js @@ -0,0 +1,71 @@ +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' }); + }); + }); +}); diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index ac3221ecab7..27d70d562c1 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -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 diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 7f7a285c453..b0d07c6e0b0 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -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) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index e8116f0a301..f7bef9e71e2 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -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) } diff --git a/spec/requests/projects/blob_controller_spec.rb b/spec/requests/projects/blob_controller_spec.rb new file mode 100644 index 00000000000..b3321375ccc --- /dev/null +++ b/spec/requests/projects/blob_controller_spec.rb @@ -0,0 +1,44 @@ +# 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 diff --git a/spec/requests/user_avatar_spec.rb b/spec/requests/user_avatar_spec.rb new file mode 100644 index 00000000000..9451674161c --- /dev/null +++ b/spec/requests/user_avatar_spec.rb @@ -0,0 +1,36 @@ +# 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 diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 561c2b572ec..61110790a43 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -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 diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb index bc95fcd6b88..b5149a0fcb1 100644 --- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb +++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb @@ -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') diff --git a/spec/support/shared_examples/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb index 914bf506320..f3f9abb7da2 100644 --- a/spec/support/shared_examples/controllers/todos_shared_examples.rb +++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb @@ -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 |