Commit ab734240 authored by Filipa Lacerda's avatar Filipa Lacerda

[ci skip] Merge branch 'master' into 42923-close-issue

* master: (21 commits)
  GitLab QA: Add GITLAB_USER_TYPE to support different login types (e.g. standard, LDAP)
  Return a warning string if we try to encode to unsupported encoding
  Remove confirmation_input
  Resolve failures in GitHub-ish import controller specs
  Remove changelogs for already-released security patches
  Merge branch 'mc/bug/38984-wildcard-protected-tags' into 'security-10-4'
  Merge branch 'fix/gh-namespace-issue' into 'security-10-4'
  Merge branch 'security-10-4-todo-api-reveals-sensitive-information' into 'security-10-4'
  Merge branch 'fix-mermaid-xss' into 'security-10-4'
  Merge branch 'security-10-4-25223-snippets-finder-doesnt-obey-feature-visibility' into 'security-10-4'
  API - fix searching in group/project specified by path
  Add documentation on how to build a QA Docker image
  Fix english in style_guide_js.md
  Adds tooltip for environment name Adds CSS for child envrionments
  Do not attach runner to a non-exsiting network in QA
  Remove not needed default statement
  Improve docs about allowing some side effects on the constructor
  Move all ENV to Runtime::Env
  Rename .scss files to use snake_case
  Moves missing branch into a vue file
  ...
parents 853c80a9 cc68b0df
<script> <script>
import Timeago from 'timeago.js'; import Timeago from 'timeago.js';
import _ from 'underscore'; import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import tooltip from '~/vue_shared/directives/tooltip';
import { humanize } from '../../lib/utils/text_utility'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue'; import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue'; import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue'; import StopComponent from './environment_stop.vue';
...@@ -21,14 +22,18 @@ ...@@ -21,14 +22,18 @@
export default { export default {
components: { components: {
userAvatarLink, UserAvatarLink,
'commit-component': CommitComponent, CommitComponent,
'actions-component': ActionsComponent, ActionsComponent,
'external-url-component': ExternalUrlComponent, ExternalUrlComponent,
'stop-component': StopComponent, StopComponent,
'rollback-component': RollbackComponent, RollbackComponent,
'terminal-button-component': TerminalButtonComponent, TerminalButtonComponent,
'monitoring-button-component': MonitoringButtonComponent, MonitoringButtonComponent,
},
directives: {
tooltip,
}, },
props: { props: {
...@@ -443,7 +448,11 @@ ...@@ -443,7 +448,11 @@
v-if="!model.isFolder" v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content" class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath"> :href="environmentPath">
<span class="flex-truncate-child">{{ model.name }}</span> <span
class="flex-truncate-child"
v-tooltip
:title="model.name"
>{{ model.name }}</span>
</a> </a>
<span <span
v-else v-else
......
...@@ -30,6 +30,9 @@ export default function renderMermaid($els) { ...@@ -30,6 +30,9 @@ export default function renderMermaid($els) {
$els.each((i, el) => { $els.each((i, el) => {
const source = el.textContent; const source = el.textContent;
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
mermaid.init(undefined, el, (id) => { mermaid.init(undefined, el, (id) => {
const svg = document.getElementById(id); const svg = document.getElementById(id);
......
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
export default {
name: 'MRWidgetMissingBranch',
props: {
mr: { type: Object, required: true },
},
directives: {
tooltip,
},
components: {
'mr-widget-merge-help': mrWidgetMergeHelp,
statusIcon,
},
computed: {
missingBranchName() {
return this.mr.sourceBranchRemoved ? 'source' : 'target';
},
message() {
return `If the ${this.missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line`;
},
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold js-branch-text">
<span class="capitalize">
{{missingBranchName}}
</span> branch does not exist.
Please restore it or use a different {{missingBranchName}} branch
<i
v-tooltip
class="fa fa-question-circle"
:title="message"
:aria-label="message"></i>
</span>
</div>
</div>
`,
};
<script>
import { sprintf, s__ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import statusIcon from '../mr_widget_status_icon.vue';
import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
export default {
name: 'MRWidgetMissingBranch',
directives: {
tooltip,
},
components: {
mrWidgetMergeHelp,
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
},
},
computed: {
missingBranchName() {
return this.mr.sourceBranchRemoved ? 'source' : 'target';
},
missingBranchNameMessage() {
return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), {
missingBranchName: this.missingBranchName,
});
},
message() {
return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), {
missingBranchName: this.missingBranchName,
});
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold js-branch-text">
<span class="capitalize">
{{ missingBranchName }}
</span> {{ s__("mrWidget|branch does not exist.") }}
{{ missingBranchNameMessage }}
<i
v-tooltip
class="fa fa-question-circle"
:title="message"
:aria-label="message"
>
</i>
</span>
</div>
</div>
</template>
...@@ -24,7 +24,7 @@ export { default as WipState } from './components/states/mr_widget_wip'; ...@@ -24,7 +24,7 @@ export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
......
<script>
import _ from 'underscore';
import { __, sprintf } from '~/locale';
export default {
props: {
inputId: {
type: String,
required: true,
},
confirmationKey: {
type: String,
required: true,
},
confirmationValue: {
type: String,
required: true,
},
shouldEscapeConfirmationValue: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
inputLabel() {
let value = this.confirmationValue;
if (this.shouldEscapeConfirmationValue) {
value = _.escape(value);
}
return sprintf(
__('Type %{value} to confirm:'),
{ value: `<code>${value}</code>` },
false,
);
},
},
methods: {
hasCorrectValue() {
return this.$refs.enteredValue.value === this.confirmationValue;
},
},
};
</script>
<template>
<div>
<label
v-html="inputLabel"
:for="inputId"
>
</label>
<input
:id="inputId"
:name="confirmationKey"
type="text"
ref="enteredValue"
class="form-control"
/>
</div>
</template>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
@import "framework/flash"; @import "framework/flash";
@import "framework/forms"; @import "framework/forms";
@import "framework/gfm"; @import "framework/gfm";
@import "framework/gitlab-theme"; @import "framework/gitlab_theme";
@import "framework/header"; @import "framework/header";
@import "framework/highlight"; @import "framework/highlight";
@import "framework/issue_box"; @import "framework/issue_box";
...@@ -35,10 +35,10 @@ ...@@ -35,10 +35,10 @@
@import "framework/pagination"; @import "framework/pagination";
@import "framework/panels"; @import "framework/panels";
@import "framework/popup"; @import "framework/popup";
@import "framework/secondary-navigation-elements"; @import "framework/secondary_navigation_elements";
@import "framework/selects"; @import "framework/selects";
@import "framework/sidebar"; @import "framework/sidebar";
@import "framework/contextual-sidebar"; @import "framework/contextual_sidebar";
@import "framework/tables"; @import "framework/tables";
@import "framework/notes"; @import "framework/notes";
@import "framework/tabs"; @import "framework/tabs";
...@@ -49,16 +49,16 @@ ...@@ -49,16 +49,16 @@
@import "framework/zen"; @import "framework/zen";
@import "framework/blank"; @import "framework/blank";
@import "framework/wells"; @import "framework/wells";
@import "framework/page-header"; @import "framework/page_header";
@import "framework/awards"; @import "framework/awards";
@import "framework/images"; @import "framework/images";
@import "framework/broadcast-messages"; @import "framework/broadcast_messages";
@import "framework/emojis"; @import "framework/emojis";
@import "framework/emoji-sprites"; @import "framework/emoji_sprites";
@import "framework/icons"; @import "framework/icons";
@import "framework/snippets"; @import "framework/snippets";
@import "framework/memory_graph"; @import "framework/memory_graph";
@import "framework/responsive_tables"; @import "framework/responsive_tables";
@import "framework/stacked-progress-bar"; @import "framework/stacked_progress_bar";
@import "framework/ci_variable_list"; @import "framework/ci_variable_list";
@import "framework/feature_highlight"; @import "framework/feature_highlight";
...@@ -121,6 +121,10 @@ ...@@ -121,6 +121,10 @@
width: 100%; width: 100%;
text-align: left; text-align: left;
} }
.environment-child-row {
padding-left: 20px;
}
} }
} }
......
...@@ -2,26 +2,16 @@ class Import::BaseController < ApplicationController ...@@ -2,26 +2,16 @@ class Import::BaseController < ApplicationController
private private
def find_or_create_namespace(names, owner) def find_or_create_namespace(names, owner)
return current_user.namespace if names == owner
return current_user.namespace unless current_user.can_create_group?
names = params[:target_namespace].presence || names names = params[:target_namespace].presence || names
full_path_namespace = Namespace.find_by_full_path(names)
return full_path_namespace if full_path_namespace return current_user.namespace if names == owner
group = Groups::NestedCreateService.new(current_user, group_path: names).execute
names.split('/').inject(nil) do |parent, name| group.errors.any? ? current_user.namespace : group
begin rescue => e
namespace = Group.create!(name: name, Gitlab::AppLogger.error(e)
path: name,
owner: current_user,
parent: parent)
namespace.add_owner(current_user)
namespace current_user.namespace
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
Namespace.where(parent: parent).find_by_path_or_name(name)
end
end
end end
end end
# Snippets Finder
#
# Used to filter Snippets collections by a set of params
#
# Arguments.
#
# current_user - The current user, nil also can be used.
# params:
# visibility (integer) - Individual snippet visibility: Public(20), internal(10) or private(0).
# project (Project) - Project related.
# author (User) - Author related.
#
# params are optional
class SnippetsFinder < UnionFinder class SnippetsFinder < UnionFinder
attr_accessor :current_user, :params include Gitlab::Allowable
attr_accessor :current_user, :params, :project
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params @params = params
@project = params[:project]
end end
def execute def execute
items = init_collection items = init_collection
items = by_project(items)
items = by_author(items) items = by_author(items)
items = by_visibility(items) items = by_visibility(items)
...@@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder ...@@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder
private private
def init_collection def init_collection
items = Snippet.all if project.present?
authorized_snippets_from_project
else
authorized_snippets
end
end
accessible(items) def authorized_snippets_from_project
if can?(current_user, :read_project_snippet, project)
if project.team.member?(current_user)
project.snippets
else
project.snippets.public_to_user(current_user)
end
else
Snippet.none
end
end end
def accessible(items) def authorized_snippets
segments = [] Snippet.where(feature_available_projects.or(not_project_related)).public_or_visible_to_user(current_user)
segments << items.public_to_user(current_user) end
segments << authorized_to_user(items) if current_user
find_union(segments, Snippet.includes(:author)) def feature_available_projects
projects = Project.public_or_visible_to_user(current_user)
.with_feature_available_for_user(:snippets, current_user).select(:id)
arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql)
table[:project_id].in(arel_query)
end end
def authorized_to_user(items) def not_project_related
items.where( table[:project_id].eq(nil)
'author_id = :author_id end
OR project_id IN (:project_ids)',
author_id: current_user.id, def table
project_ids: current_user.authorized_projects.select(:id)) Snippet.arel_table
end end
def by_visibility(items) def by_visibility(items)
...@@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder ...@@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder
items.where(author_id: params[:author].id) items.where(author_id: params[:author].id)
end end
def by_project(items)
return items unless params[:project]
items.where(project_id: params[:project].id)
end
def visibility_from_scope def visibility_from_scope
case params[:scope].to_s case params[:scope].to_s
when 'are_private' when 'are_private'
......
...@@ -1589,8 +1589,11 @@ class Project < ActiveRecord::Base ...@@ -1589,8 +1589,11 @@ class Project < ActiveRecord::Base
end end
def protected_for?(ref) def protected_for?(ref)
ProtectedBranch.protected?(self, ref) || if repository.branch_exists?(ref)
ProtectedBranch.protected?(self, ref)
elsif repository.tag_exists?(ref)
ProtectedTag.protected?(self, ref) ProtectedTag.protected?(self, ref)
end
end end
def deployment_variables def deployment_variables
......
...@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base ...@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/) @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end end
# Returns a collection of snippets that are either public or visible to the
# logged in user.
#
# This method does not verify the user actually has the access to the project
# the snippet is in, so it should be only used on a relation that's already scoped
# for project access
def self.public_or_visible_to_user(user = nil)
if user
authorized = user
.project_authorizations
.select(1)
.where('project_authorizations.project_id = snippets.project_id')
levels = Gitlab::VisibilityLevel.levels_for_user(user)
where('EXISTS (?) OR snippets.visibility_level IN (?) or snippets.author_id = (?)', authorized, levels, user.id)
else
public_to_user
end
end
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
......
...@@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy ...@@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy
enable :create_note enable :create_note
enable :upload_file enable :upload_file
enable :read_cycle_analytics enable :read_cycle_analytics
enable :read_project_snippet
end end
rule { can?(:reporter_access) }.policy do rule { can?(:reporter_access) }.policy do
......
...@@ -11,8 +11,8 @@ module Groups ...@@ -11,8 +11,8 @@ module Groups
def execute def execute
return nil unless group_path return nil unless group_path
if group = Group.find_by_full_path(group_path) if namespace = namespace_or_group(group_path)
return group return namespace
end end
if group_path.include?('/') && !Group.supports_nested_groups? if group_path.include?('/') && !Group.supports_nested_groups?
...@@ -40,10 +40,14 @@ module Groups ...@@ -40,10 +40,14 @@ module Groups
) )
new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility new_params[:visibility_level] ||= Gitlab::CurrentSettings.current_application_settings.default_group_visibility
last_group = Group.find_by_full_path(partial_path) || Groups::CreateService.new(current_user, new_params).execute last_group = namespace_or_group(partial_path) || Groups::CreateService.new(current_user, new_params).execute
end end
last_group last_group
end end
def namespace_or_group(group_path)
Namespace.find_by_full_path(group_path)
end
end end
end end
---
title: Adds tooltip in environment names to increase readability
merge_request:
author:
type: fixed
...@@ -207,10 +207,39 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod ...@@ -207,10 +207,39 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
var c = pureFunction(values.foo); var c = pureFunction(values.foo);
``` ```
1. Avoid constructors with side-effects 1. Avoid constructors with side-effects.
Although we aim for code without side-effects we need some side-effects for our code to run.
If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
```javascript
// Bad
export class Foo {
constructor() {
this.init();
}
init() {
document.addEventListener('click', this.handleCallback)
},
handleCallback() {
}
}
// Good
export class Foo {
constructor() {
document.addEventListener()
}
handleCallback() {
}
}
```
On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized oustside of the constructor.
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach` 1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
A forEach will cause side effects, it will be mutating the array being iterated. Prefer using `.map`, A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
`.reduce` or `.filter` `.reduce` or `.filter`
```javascript ```javascript
const users = [ { name: 'Foo' }, { name: 'Bar' } ]; const users = [ { name: 'Foo' }, { name: 'Bar' } ];
......
...@@ -85,9 +85,9 @@ module API ...@@ -85,9 +85,9 @@ module API
use :pagination use :pagination
end end
get ':id/-/search' do get ':id/-/search' do
find_group!(params[:id]) group = find_group!(params[:id])
present search(group_id: params[:id]), with: entity present search(group_id: group.id), with: entity
end end
end end
...@@ -106,9 +106,9 @@ module API ...@@ -106,9 +106,9 @@ module API
use :pagination use :pagination
end end
get ':id/-/search' do get ':id/-/search' do
find_project!(params[:id]) project = find_project!(params[:id])
present search(project_id: params[:id]), with: entity present search(project_id: project.id), with: entity
end end
end end
end end
......
...@@ -60,7 +60,7 @@ module API ...@@ -60,7 +60,7 @@ module API
end end
post ':id/mark_as_done' do post ':id/mark_as_done' do
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
todo = Todo.find(params[:id]) todo = current_user.todos.find(params[:id])
present todo, with: Entities::Todo, current_user: current_user present todo, with: Entities::Todo, current_user: current_user
end end
......
...@@ -12,7 +12,7 @@ module API ...@@ -12,7 +12,7 @@ module API
end end
delete ':id' do delete ':id' do
TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user) TodoService.new.mark_todos_as_done_by_ids(params[:id], current_user)
todo = Todo.find(params[:id]) todo = current_user.todos.find(params[:id])
present todo, with: ::API::Entities::Todo, current_user: current_user present todo, with: ::API::Entities::Todo, current_user: current_user
end end
......
...@@ -14,23 +14,33 @@ module Banzai ...@@ -14,23 +14,33 @@ module Banzai
end end
def highlight_node(node) def highlight_node(node)
code = node.text
css_classes = 'code highlight js-syntax-highlight' css_classes = 'code highlight js-syntax-highlight'
language = node.attr('lang') lang = node.attr('lang')
retried = false
if use_rouge?(language) if use_rouge?(lang)
lexer = lexer_for(language) lexer = lexer_for(lang)
language = lexer.tag language = lexer.tag
else
lexer = Rouge::Lexers::PlainText.new
language = lang
end
begin
code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, node.text), tag: language)
css_classes << " #{language}" if language
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure users can
# still access an issue/comment/etc. First, retry with the plain text
# filter. If that fails, then just skip this entirely, but that would
# be a pretty bad upstream bug.
return if retried
begin language = nil
code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: language) lexer = Rouge::Lexers::PlainText.new
css_classes << " #{language}" retried = true
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc.
language = nil retry
end
end end
highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>) highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
......
...@@ -28,9 +28,9 @@ module Gitlab ...@@ -28,9 +28,9 @@ module Gitlab
# encode and clean the bad chars # encode and clean the bad chars
message.replace clean(message) message.replace clean(message)
rescue ArgumentError rescue ArgumentError => e
return nil return unless e.message.include?('unknown encoding name')
rescue
encoding = detect ? detect[:encoding] : "unknown" encoding = detect ? detect[:encoding] : "unknown"
"--broken encoding: #{encoding}" "--broken encoding: #{encoding}"
end end
......
...@@ -115,7 +115,7 @@ namespace :gemojione do ...@@ -115,7 +115,7 @@ namespace :gemojione do
end end
end end
style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss)) style_path = Rails.root.join(*%w(app assets stylesheets framework emoji_sprites.scss))
# Combine the resized assets into a packed sprite and re-generate the SCSS # Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')" SpriteFactory.cssurl = "image-url('$IMAGE')"
......
...@@ -70,6 +70,30 @@ If you need to authenticate as a different user, you can provide the ...@@ -70,6 +70,30 @@ If you need to authenticate as a different user, you can provide the
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com
``` ```
If your user doesn't have permission to default sandbox group
`gitlab-qa-sandbox`, you could also use another sandbox group by giving
`GITLAB_SANDBOX_NAME`:
```
GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com
```
In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user:
```
GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com
```
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables). All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables).
### Building a Docker image to test
Once you have made changes to the CE/EE repositories, you may want to build a
Docker image to test locally instead of waiting for the `gitlab-ce-qa` or
`gitlab-ee-qa` nightly builds. To do that, you can run from this directory:
```sh
docker build -t gitlab/gitlab-ce-qa:nightly .
```
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/ [GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
...@@ -39,6 +39,14 @@ module QA ...@@ -39,6 +39,14 @@ module QA
end end
end end
def sign_in_using_credentials
if Runtime::User.ldap_user?
sign_in_using_ldap_credentials
else
sign_in_using_gitlab_credentials
end
end
def sign_in_using_ldap_credentials def sign_in_using_ldap_credentials
using_wait_time 0 do using_wait_time 0 do
set_initial_password_if_present set_initial_password_if_present
...@@ -51,7 +59,7 @@ module QA ...@@ -51,7 +59,7 @@ module QA
end end
end end
def sign_in_using_credentials def sign_in_using_gitlab_credentials
using_wait_time 0 do using_wait_time 0 do
set_initial_password_if_present set_initial_password_if_present
......
...@@ -16,6 +16,28 @@ module QA ...@@ -16,6 +16,28 @@ module QA
def personal_access_token def personal_access_token
ENV['PERSONAL_ACCESS_TOKEN'] ENV['PERSONAL_ACCESS_TOKEN']
end end
# By default, "standard" denotes a standard GitLab user login.
# Set this to "ldap" if the user should be logged in via LDAP.
def user_type
(ENV['GITLAB_USER_TYPE'] || 'standard').tap do |type|
unless %w(ldap standard).include?(type)
raise ArgumentError.new("Invalid user type '#{type}': must be 'ldap' or 'standard'")
end
end
end
def user_username
ENV['GITLAB_USERNAME']
end
def user_password
ENV['GITLAB_PASSWORD']
end
def sandbox_name
ENV['GITLAB_SANDBOX_NAME']
end
end end
end end
end end
...@@ -16,7 +16,7 @@ module QA ...@@ -16,7 +16,7 @@ module QA
end end
def sandbox_name def sandbox_name
'gitlab-qa-sandbox' Runtime::Env.sandbox_name || 'gitlab-qa-sandbox'
end end
end end
end end
......
...@@ -4,11 +4,15 @@ module QA ...@@ -4,11 +4,15 @@ module QA
extend self extend self
def name def name
ENV['GITLAB_USERNAME'] || 'root' Runtime::Env.user_username || 'root'
end end
def password def password
ENV['GITLAB_PASSWORD'] || '5iveL!fe' Runtime::Env.user_password || '5iveL!fe'
end
def ldap_user?
Runtime::Env.user_type == 'ldap'
end end
end end
end end
......
...@@ -15,6 +15,14 @@ module QA ...@@ -15,6 +15,14 @@ module QA
@tags = %w[qa test] @tags = %w[qa test]
end end
def network
shell "docker network inspect #{@network}"
rescue CommandError
'bridge'
else
@network
end
def pull def pull
shell "docker pull #{@image}" shell "docker pull #{@image}"
end end
...@@ -22,7 +30,7 @@ module QA ...@@ -22,7 +30,7 @@ module QA
def register! def register!
shell <<~CMD.tr("\n", ' ') shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh docker run -d --rm --entrypoint=/bin/sh
--network #{@network} --name #{@name} --network #{network} --name #{@name}
-e CI_SERVER_URL=#{@address} -e CI_SERVER_URL=#{@address}
-e REGISTER_NON_INTERACTIVE=true -e REGISTER_NON_INTERACTIVE=true
-e REGISTRATION_TOKEN=#{@token} -e REGISTRATION_TOKEN=#{@token}
......
...@@ -3,6 +3,8 @@ require 'open3' ...@@ -3,6 +3,8 @@ require 'open3'
module QA module QA
module Service module Service
module Shellout module Shellout
CommandError = Class.new(StandardError)
## ##
# TODO, make it possible to use generic QA framework classes # TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94 # as a library - gitlab-org/gitlab-qa#94
...@@ -14,7 +16,7 @@ module QA ...@@ -14,7 +16,7 @@ module QA
out.each { |line| puts line } out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero? if wait.value.exited? && wait.value.exitstatus.nonzero?
raise "Command `#{command}` failed!" raise CommandError, "Command `#{command}` failed!"
end end
end end
end end
......
...@@ -55,4 +55,25 @@ describe QA::Runtime::Env do ...@@ -55,4 +55,25 @@ describe QA::Runtime::Env do
end end
end end
end end
describe '.user_type' do
it 'returns standard if not defined' do
expect(described_class.user_type).to eq('standard')
end
it 'returns standard as defined' do
stub_env('GITLAB_USER_TYPE', 'standard')
expect(described_class.user_type).to eq('standard')
end
it 'returns ldap as defined' do
stub_env('GITLAB_USER_TYPE', 'ldap')
expect(described_class.user_type).to eq('ldap')
end
it 'returns an error if invalid user type' do
stub_env('GITLAB_USER_TYPE', 'foobar')
expect { described_class.user_type }.to raise_error(ArgumentError)
end
end
end end
...@@ -222,7 +222,7 @@ describe Import::BitbucketController do ...@@ -222,7 +222,7 @@ describe Import::BitbucketController do
end end
end end
context 'user has chosen an existing nested namespace and name for the project' do context 'user has chosen an existing nested namespace and name for the project', :postgresql do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
...@@ -240,7 +240,7 @@ describe Import::BitbucketController do ...@@ -240,7 +240,7 @@ describe Import::BitbucketController do
end end
end end
context 'user has chosen a non-existent nested namespaces and name for the project' do context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
...@@ -271,10 +271,14 @@ describe Import::BitbucketController do ...@@ -271,10 +271,14 @@ describe Import::BitbucketController do
end end
end end
context 'user has chosen existent and non-existent nested namespaces and name for the project' do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator) expect(Gitlab::BitbucketImport::ProjectCreator)
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params) .to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
......
...@@ -195,7 +195,7 @@ describe Import::GitlabController do ...@@ -195,7 +195,7 @@ describe Import::GitlabController do
end end
end end
context 'user has chosen an existing nested namespace for the project' do context 'user has chosen an existing nested namespace for the project', :postgresql do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
...@@ -212,7 +212,7 @@ describe Import::GitlabController do ...@@ -212,7 +212,7 @@ describe Import::GitlabController do
end end
end end
context 'user has chosen a non-existent nested namespaces for the project' do context 'user has chosen a non-existent nested namespaces for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
...@@ -243,10 +243,14 @@ describe Import::GitlabController do ...@@ -243,10 +243,14 @@ describe Import::GitlabController do
end end
end end
context 'user has chosen existent and non-existent nested namespaces and name for the project' do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator) expect(Gitlab::GitlabImport::ProjectCreator)
.to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params) .to receive(:new).with(gitlab_repo, kind_of(Namespace), user, access_params)
......
require 'spec_helper'
describe 'Math rendering', :js do
it 'renders inline and display math correctly' do
description = <<~MATH
This math is inline $`a^2+b^2=c^2`$.
This is on a separate line
```math
a^2+b^2=c^2
```
MATH
project = create(:project, :public)
issue = create(:issue, project: project, description: description)
visit project_issue_path(project, issue)
expect(page).to have_selector('.katex .mord.mathit', text: 'b')
expect(page).to have_selector('.katex-display .mord.mathit', text: 'b')
end
end
require 'spec_helper'
describe 'Mermaid rendering', :js do
it 'renders Mermaid diagrams correctly' do
description = <<~MERMAID
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
MERMAID
project = create(:project, :public)
issue = create(:issue, project: project, description: description)
visit project_issue_path(project, issue)
%w[A B C D].each do |label|
expect(page).to have_selector('svg foreignObject', text: label)
end
end
end
require 'spec_helper' require 'spec_helper'
describe SnippetsFinder do describe SnippetsFinder do
let(:user) { create :user } include Gitlab::Allowable
let(:user1) { create :user } using RSpec::Parameterized::TableSyntax
let(:group) { create :group, :public }
let(:project1) { create(:project, :public, group: group) }
let(:project2) { create(:project, :private, group: group) }
context 'all snippets visible to a user' do
let!(:snippet1) { create(:personal_snippet, :private) }
let!(:snippet2) { create(:personal_snippet, :internal) }
let!(:snippet3) { create(:personal_snippet, :public) }
let!(:project_snippet1) { create(:project_snippet, :private) }
let!(:project_snippet2) { create(:project_snippet, :internal) }
let!(:project_snippet3) { create(:project_snippet, :public) }
it "returns all private and internal snippets" do
snippets = described_class.new(user, scope: :all).execute
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
expect(snippets).not_to include(snippet1, project_snippet1)
end
it "returns all public snippets" do
snippets = described_class.new(nil, scope: :all).execute
expect(snippets).to include(snippet3, project_snippet3)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
it "returns all public and internal snippets for normal user" do
snippets = described_class.new(user).execute
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
expect(snippets).not_to include(snippet1, project_snippet1)
end
it "returns all public snippets for non authorized user" do
snippets = described_class.new(nil).execute
expect(snippets).to include(snippet3, project_snippet3)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
it "returns all public and authored snippets for external user" do
external_user = create(:user, :external)
authored_snippet = create(:personal_snippet, :internal, author: external_user)
snippets = described_class.new(external_user).execute
expect(snippets).to include(snippet3, project_snippet3, authored_snippet)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
end
context 'filter by visibility' do context 'filter by visibility' do
let!(:snippet1) { create(:personal_snippet, :private) } let!(:snippet1) { create(:personal_snippet, :private) }
...@@ -67,6 +18,7 @@ describe SnippetsFinder do ...@@ -67,6 +18,7 @@ describe SnippetsFinder do
end end
context 'filter by scope' do context 'filter by scope' do
let(:user) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) }
...@@ -84,7 +36,7 @@ describe SnippetsFinder do ...@@ -84,7 +36,7 @@ describe SnippetsFinder do
expect(snippets).not_to include(snippet2, snippet3) expect(snippets).not_to include(snippet2, snippet3)
end end
it "returns all snippets for 'are_interna;' scope" do it "returns all snippets for 'are_internal' scope" do
snippets = described_class.new(user, scope: :are_internal).execute snippets = described_class.new(user, scope: :are_internal).execute
expect(snippets).to include(snippet2) expect(snippets).to include(snippet2)
...@@ -100,6 +52,8 @@ describe SnippetsFinder do ...@@ -100,6 +52,8 @@ describe SnippetsFinder do
end end
context 'filter by author' do context 'filter by author' do
let(:user) { create :user }
let(:user1) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) }
...@@ -147,6 +101,10 @@ describe SnippetsFinder do ...@@ -147,6 +101,10 @@ describe SnippetsFinder do
end end
context 'filter by project' do context 'filter by project' do
let(:user) { create :user }
let(:group) { create :group, :public }
let(:project1) { create(:project, :public, group: group) }
before do before do
@snippet1 = create(:project_snippet, :private, project: project1) @snippet1 = create(:project_snippet, :private, project: project1)
@snippet2 = create(:project_snippet, :internal, project: project1) @snippet2 = create(:project_snippet, :internal, project: project1)
...@@ -203,4 +161,9 @@ describe SnippetsFinder do ...@@ -203,4 +161,9 @@ describe SnippetsFinder do
expect(snippets).to include(@snippet1) expect(snippets).to include(@snippet1)
end end
end end
describe "#execute" do
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
include_examples 'snippet visibility', described_class
end
end end
import Vue from 'vue'; import Vue from 'vue';
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch'; import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
const createComponent = () => {
const Component = Vue.extend(missingBranchComponent);
const mr = {
sourceBranchRemoved: true,
};
return new Component({
el: document.createElement('div'),
propsData: { mr },
});
};
describe('MRWidgetMissingBranch', () => { describe('MRWidgetMissingBranch', () => {
describe('props', () => { let vm;
it('should have props', () => {
const mrProp = missingBranchComponent.props.mr;
expect(mrProp.type instanceof Object).toBeTruthy(); beforeEach(() => {
expect(mrProp.required).toBeTruthy(); const Component = Vue.extend(missingBranchComponent);
}); vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } });
}); });
describe('components', () => { afterEach(() => {
it('should have components added', () => { vm.$destroy();
expect(missingBranchComponent.components['mr-widget-merge-help']).toBeDefined();
});
}); });
describe('computed', () => { describe('computed', () => {
describe('missingBranchName', () => { describe('missingBranchName', () => {
it('should return proper branch name', () => { it('should return proper branch name', () => {
const vm = createComponent();
expect(vm.missingBranchName).toEqual('source'); expect(vm.missingBranchName).toEqual('source');
vm.mr.sourceBranchRemoved = false; vm.mr.sourceBranchRemoved = false;
...@@ -43,7 +27,7 @@ describe('MRWidgetMissingBranch', () => { ...@@ -43,7 +27,7 @@ describe('MRWidgetMissingBranch', () => {
describe('template', () => { describe('template', () => {
it('should have correct elements', () => { it('should have correct elements', () => {
const el = createComponent().$el; const el = vm.$el;
const content = el.textContent.replace(/\n(\s)+/g, ' ').trim(); const content = el.textContent.replace(/\n(\s)+/g, ' ').trim();
expect(el.classList.contains('mr-widget-body')).toBeTruthy(); expect(el.classList.contains('mr-widget-body')).toBeTruthy();
......
import Vue from 'vue';
import confirmationInput from '~/vue_shared/components/confirmation_input.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Confirmation input component', () => {
const Component = Vue.extend(confirmationInput);
const props = {
inputId: 'dummy-id',
confirmationKey: 'confirmation-key',
confirmationValue: 'confirmation-value',
};
let vm;
afterEach(() => {
vm.$destroy();
});
describe('props', () => {
beforeEach(() => {
vm = mountComponent(Component, props);
});
it('sets id of the input field to inputId', () => {
expect(vm.$refs.enteredValue.id).toBe(props.inputId);
});
it('sets name of the input field to confirmationKey', () => {
expect(vm.$refs.enteredValue.name).toBe(props.confirmationKey);
});
});
describe('computed', () => {
describe('inputLabel', () => {
it('escapes confirmationValue by default', () => {
vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng' });
expect(vm.inputLabel).toBe('Type <code>n&lt;e&gt;&lt;/e&gt;ds escap&quot;ng</code> to confirm:');
});
it('does not escape confirmationValue if escapeValue is false', () => {
vm = mountComponent(Component, { ...props, confirmationValue: 'n<e></e>ds escap"ng', shouldEscapeConfirmationValue: false });
expect(vm.inputLabel).toBe('Type <code>n<e></e>ds escap"ng</code> to confirm:');
});
});
});
describe('methods', () => {
describe('hasCorrectValue', () => {
beforeEach(() => {
vm = mountComponent(Component, props);
});
it('returns false if entered value is incorrect', () => {
vm.$refs.enteredValue.value = 'incorrect';
expect(vm.hasCorrectValue()).toBe(false);
});
it('returns true if entered value is correct', () => {
vm.$refs.enteredValue.value = props.confirmationValue;
expect(vm.hasCorrectValue()).toBe(true);
});
});
});
});
...@@ -3,35 +3,86 @@ require 'spec_helper' ...@@ -3,35 +3,86 @@ require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter do describe Banzai::Filter::SyntaxHighlightFilter do
include FilterSpecHelper include FilterSpecHelper
shared_examples "XSS prevention" do |lang|
it "escapes HTML tags" do
# This is how a script tag inside a code block is presented to this filter
# after Markdown rendering.
result = filter(%{<pre lang="#{lang}"><code>&lt;script&gt;alert(1)&lt;/script&gt;</code></pre>})
expect(result.to_html).not_to include("<script>alert(1)</script>")
expect(result.to_html).to include("alert(1)")
end
end
context "when no language is specified" do context "when no language is specified" do
it "highlights as plaintext" do it "highlights as plaintext" do
result = filter('<pre><code>def fun end</code></pre>') result = filter('<pre><code>def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>') expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>')
end end
include_examples "XSS prevention", ""
end end
context "when a valid language is specified" do context "when a valid language is specified" do
it "highlights as that language" do it "highlights as that language" do
result = filter('<pre><code lang="ruby">def fun end</code></pre>') result = filter('<pre><code lang="ruby">def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>') expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>')
end end
include_examples "XSS prevention", "ruby"
end end
context "when an invalid language is specified" do context "when an invalid language is specified" do
it "highlights as plaintext" do it "highlights as plaintext" do
result = filter('<pre><code lang="gnuplot">This is a test</code></pre>') result = filter('<pre><code lang="gnuplot">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>') expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>')
end end
include_examples "XSS prevention", "gnuplot"
end end
context "when Rouge formatting fails" do context "languages that should be passed through" do
%w(math mermaid plantuml).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
end
include_examples "XSS prevention", lang
end
end
end
context "when Rouge lexing fails" do
before do before do
allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError) allow_any_instance_of(Rouge::Lexers::Ruby).to receive(:stream_tokens).and_raise(StandardError)
end end
it "highlights as plaintext" do it "highlights as plaintext" do
result = filter('<pre><code lang="ruby">This is a test</code></pre>') result = filter('<pre><code lang="ruby">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight" lang="" v-pre="true"><code><span id="LC1" class="line" lang="">This is a test</span></code></pre>')
end
include_examples "XSS prevention", "ruby"
end
context "when Rouge lexing fails after a retry" do
before do
allow_any_instance_of(Rouge::Lexers::PlainText).to receive(:stream_tokens).and_raise(StandardError)
end
it "does not add highlighting classes" do
result = filter('<pre><code>This is a test</code></pre>')
expect(result.to_html).to eq('<pre><code>This is a test</code></pre>')
end end
include_examples "XSS prevention", "ruby"
end end
end end
...@@ -24,6 +24,11 @@ describe Gitlab::EncodingHelper do ...@@ -24,6 +24,11 @@ describe Gitlab::EncodingHelper do
'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.', 'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
"mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'), "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
"mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi " "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi "
],
[
'string with detected encoding that is not supported in Ruby',
"\xFFe,i\xFF,\xB8oi,'\xB8,\xFF,-",
"--broken encoding: IBM420_ltr"
] ]
].each do |description, test_string, xpect| ].each do |description, test_string, xpect|
it description do it description do
......
...@@ -1590,7 +1590,7 @@ describe Ci::Build do ...@@ -1590,7 +1590,7 @@ describe Ci::Build do
context 'when the branch is protected' do context 'when the branch is protected' do
before do before do
create(:protected_branch, project: build.project, name: build.ref) allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
...@@ -1598,7 +1598,7 @@ describe Ci::Build do ...@@ -1598,7 +1598,7 @@ describe Ci::Build do
context 'when the tag is protected' do context 'when the tag is protected' do
before do before do
create(:protected_tag, project: build.project, name: build.ref) allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
...@@ -1635,7 +1635,7 @@ describe Ci::Build do ...@@ -1635,7 +1635,7 @@ describe Ci::Build do
context 'when the branch is protected' do context 'when the branch is protected' do
before do before do
create(:protected_branch, project: build.project, name: build.ref) allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
...@@ -1643,7 +1643,7 @@ describe Ci::Build do ...@@ -1643,7 +1643,7 @@ describe Ci::Build do
context 'when the tag is protected' do context 'when the tag is protected' do
before do before do
create(:protected_tag, project: build.project, name: build.ref) allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true)
end end
it { is_expected.to include(protected_variable) } it { is_expected.to include(protected_variable) }
......
...@@ -549,7 +549,7 @@ describe Group do ...@@ -549,7 +549,7 @@ describe Group do
context 'when the ref is a protected branch' do context 'when the ref is a protected branch' do
before do before do
create(:protected_branch, name: 'ref', project: project) allow(project).to receive(:protected_for?).with('ref').and_return(true)
end end
it_behaves_like 'ref is protected' it_behaves_like 'ref is protected'
...@@ -557,7 +557,7 @@ describe Group do ...@@ -557,7 +557,7 @@ describe Group do
context 'when the ref is a protected tag' do context 'when the ref is a protected tag' do
before do before do
create(:protected_tag, name: 'ref', project: project) allow(project).to receive(:protected_for?).with('ref').and_return(true)
end end
it_behaves_like 'ref is protected' it_behaves_like 'ref is protected'
...@@ -571,6 +571,10 @@ describe Group do ...@@ -571,6 +571,10 @@ describe Group do
let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) } let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) } let(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
before do
allow(project).to receive(:protected_for?).with('ref').and_return(true)
end
it 'returns all variables belong to the group and parent groups' do it 'returns all variables belong to the group and parent groups' do
expected_array1 = [protected_variable, secret_variable] expected_array1 = [protected_variable, secret_variable]
expected_array2 = [variable_child, variable_child_2, variable_child_3] expected_array2 = [variable_child, variable_child_2, variable_child_3]
......
...@@ -2092,7 +2092,7 @@ describe Project do ...@@ -2092,7 +2092,7 @@ describe Project do
context 'when the ref is a protected branch' do context 'when the ref is a protected branch' do
before do before do
create(:protected_branch, name: 'ref', project: project) allow(project).to receive(:protected_for?).with('ref').and_return(true)
end end
it_behaves_like 'ref is protected' it_behaves_like 'ref is protected'
...@@ -2100,7 +2100,7 @@ describe Project do ...@@ -2100,7 +2100,7 @@ describe Project do
context 'when the ref is a protected tag' do context 'when the ref is a protected tag' do
before do before do
create(:protected_tag, name: 'ref', project: project) allow(project).to receive(:protected_for?).with('ref').and_return(true)
end end
it_behaves_like 'ref is protected' it_behaves_like 'ref is protected'
...@@ -2125,6 +2125,8 @@ describe Project do ...@@ -2125,6 +2125,8 @@ describe Project do
context 'when the ref is a protected branch' do context 'when the ref is a protected branch' do
before do before do
allow(project).to receive(:repository).and_call_original
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true)
create(:protected_branch, name: 'ref', project: project) create(:protected_branch, name: 'ref', project: project)
end end
...@@ -2135,6 +2137,8 @@ describe Project do ...@@ -2135,6 +2137,8 @@ describe Project do
context 'when the ref is a protected tag' do context 'when the ref is a protected tag' do
before do before do
allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false)
allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true)
create(:protected_tag, name: 'ref', project: project) create(:protected_tag, name: 'ref', project: project)
end end
......
require 'spec_helper' require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe PersonalSnippetPolicy do describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) } let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
......
require 'spec_helper' require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe ProjectSnippetPolicy do describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) } let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
......
...@@ -180,6 +180,18 @@ describe API::Search do ...@@ -180,6 +180,18 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones' it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end end
context 'for milestones scope with group path as id' do
before do
another_project = create(:project, :public)
create(:milestone, project: project, title: 'awesome milestone')
create(:milestone, project: another_project, title: 'awesome milestone other project')
get api("/groups/#{CGI.escape(group.full_path)}/-/search", user), scope: 'milestones', search: 'awesome'
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
end end
end end
...@@ -286,6 +298,14 @@ describe API::Search do ...@@ -286,6 +298,14 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/commits' it_behaves_like 'response is correct', schema: 'public_api/v4/commits'
end end
context 'for commits scope with project path as id' do
before do
get api("/projects/#{CGI.escape(repo_project.full_path)}/-/search", user), scope: 'commits', search: '498214de67004b1da3d820901307bed2a68a8ef6'
end
it_behaves_like 'response is correct', schema: 'public_api/v4/commits'
end
context 'for blobs scope' do context 'for blobs scope' do
before do before do
get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors' get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors'
......
...@@ -32,6 +32,27 @@ describe API::Snippets do ...@@ -32,6 +32,27 @@ describe API::Snippets do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(0) expect(json_response.size).to eq(0)
end end
it 'returns 404 for non-authenticated' do
create(:personal_snippet, :internal)
get api("/snippets/")
expect(response).to have_gitlab_http_status(401)
end
it 'does not return snippets related to a project with disable feature visibility' do
project = create(:project)
create(:project_member, project: project, user: user)
public_snippet = create(:personal_snippet, :public, author: user, project: project)
project.project_feature.update_attribute(:snippets_access_level, 0)
get api("/snippets/", user)
json_response.each do |snippet|
expect(snippet["id"]).not_to eq(public_snippet.id)
end
end
end end
describe 'GET /snippets/public' do describe 'GET /snippets/public' do
......
...@@ -129,6 +129,12 @@ describe API::Todos do ...@@ -129,6 +129,12 @@ describe API::Todos do
post api("/todos/#{pending_1.id}/mark_as_done", john_doe) post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
end end
it 'returns 404 if the todo does not belong to the current user' do
post api("/todos/#{pending_1.id}/mark_as_done", author_1)
expect(response.status).to eq(404)
end
end end
end end
......
...@@ -38,6 +38,12 @@ describe API::V3::Todos do ...@@ -38,6 +38,12 @@ describe API::V3::Todos do
delete v3_api("/todos/#{pending_1.id}", john_doe) delete v3_api("/todos/#{pending_1.id}", john_doe)
end end
it 'returns 404 if the todo does not belong to the current user' do
delete v3_api("/todos/#{pending_1.id}", author_1)
expect(response.status).to eq(404)
end
end end
end end
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Search::SnippetService do describe Search::SnippetService do
let(:author) { create(:author) } let(:author) { create(:author) }
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') }
let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') }
......
...@@ -256,7 +256,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -256,7 +256,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end end
end end
context 'user has chosen an existing nested namespace and name for the project' do context 'user has chosen an existing nested namespace and name for the project', :postgresql do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
...@@ -274,7 +274,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -274,7 +274,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end end
end end
context 'user has chosen a non-existent nested namespaces and name for the project' do context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
...@@ -305,10 +305,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -305,10 +305,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end end
end end
context 'user has chosen existent and non-existent nested namespaces and name for the project' do context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator) expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider) .to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
...@@ -325,6 +329,53 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -325,6 +329,53 @@ shared_examples 'a GitHub-ish import controller: POST create' do
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } } expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
.to change { Namespace.count }.by(2) .to change { Namespace.count }.by(2)
end end
it 'does not create a new namespace under the user namespace' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
expect { post :create, { target_namespace: "#{user.namespace_path}/test_group", new_name: test_name, format: :js } }
.not_to change { Namespace.count }
end
end
context 'user cannot create a subgroup inside a group is not a member of' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
it 'does not take the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js }
end
it 'does not create the namespaces' do
allow(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, kind_of(Namespace), user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :js } }
.not_to change { Namespace.count }
end
end
context 'user can use a group without having permissions to create a group' do
let(:test_name) { 'test_name' }
let!(:group) { create(:group, name: 'foo') }
it 'takes the selected namespace and name' do
group.add_owner(user)
user.update!(can_create_group: false)
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.to receive(:new).with(provider_repo, test_name, group, user, access_params, type: provider)
.and_return(double(execute: build_stubbed(:project)))
post :create, { target_namespace: 'foo', new_name: test_name, format: :js }
end
end end
context 'when user can not create projects in the chosen namespace' do context 'when user can not create projects in the chosen namespace' do
......
This diff is collapsed.
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