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>
import Timeago from 'timeago.js';
import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '../../lib/utils/text_utility';
import tooltip from '~/vue_shared/directives/tooltip';
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 ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
......@@ -21,14 +22,18 @@
export default {
components: {
userAvatarLink,
'commit-component': CommitComponent,
'actions-component': ActionsComponent,
'external-url-component': ExternalUrlComponent,
'stop-component': StopComponent,
'rollback-component': RollbackComponent,
'terminal-button-component': TerminalButtonComponent,
'monitoring-button-component': MonitoringButtonComponent,
UserAvatarLink,
CommitComponent,
ActionsComponent,
ExternalUrlComponent,
StopComponent,
RollbackComponent,
TerminalButtonComponent,
MonitoringButtonComponent,
},
directives: {
tooltip,
},
props: {
......@@ -443,7 +448,11 @@
v-if="!model.isFolder"
class="environment-name flex-truncate-parent table-mobile-content"
:href="environmentPath">
<span class="flex-truncate-child">{{ model.name }}</span>
<span
class="flex-truncate-child"
v-tooltip
:title="model.name"
>{{ model.name }}</span>
</a>
<span
v-else
......
......@@ -30,6 +30,9 @@ export default function renderMermaid($els) {
$els.each((i, el) => {
const source = el.textContent;
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
mermaid.init(undefined, el, (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';
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 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 ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
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 @@
@import "framework/flash";
@import "framework/forms";
@import "framework/gfm";
@import "framework/gitlab-theme";
@import "framework/gitlab_theme";
@import "framework/header";
@import "framework/highlight";
@import "framework/issue_box";
......@@ -35,10 +35,10 @@
@import "framework/pagination";
@import "framework/panels";
@import "framework/popup";
@import "framework/secondary-navigation-elements";
@import "framework/secondary_navigation_elements";
@import "framework/selects";
@import "framework/sidebar";
@import "framework/contextual-sidebar";
@import "framework/contextual_sidebar";
@import "framework/tables";
@import "framework/notes";
@import "framework/tabs";
......@@ -49,16 +49,16 @@
@import "framework/zen";
@import "framework/blank";
@import "framework/wells";
@import "framework/page-header";
@import "framework/page_header";
@import "framework/awards";
@import "framework/images";
@import "framework/broadcast-messages";
@import "framework/broadcast_messages";
@import "framework/emojis";
@import "framework/emoji-sprites";
@import "framework/emoji_sprites";
@import "framework/icons";
@import "framework/snippets";
@import "framework/memory_graph";
@import "framework/responsive_tables";
@import "framework/stacked-progress-bar";
@import "framework/stacked_progress_bar";
@import "framework/ci_variable_list";
@import "framework/feature_highlight";
......@@ -121,6 +121,10 @@
width: 100%;
text-align: left;
}
.environment-child-row {
padding-left: 20px;
}
}
}
......
......@@ -2,26 +2,16 @@ class Import::BaseController < ApplicationController
private
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
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|
begin
namespace = Group.create!(name: name,
path: name,
owner: current_user,
parent: parent)
namespace.add_owner(current_user)
group.errors.any? ? current_user.namespace : group
rescue => e
Gitlab::AppLogger.error(e)
namespace
rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
Namespace.where(parent: parent).find_by_path_or_name(name)
end
end
current_user.namespace
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
attr_accessor :current_user, :params
include Gitlab::Allowable
attr_accessor :current_user, :params, :project
def initialize(current_user, params = {})
@current_user = current_user
@params = params
@project = params[:project]
end
def execute
items = init_collection
items = by_project(items)
items = by_author(items)
items = by_visibility(items)
......@@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder
private
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
def accessible(items)
segments = []
segments << items.public_to_user(current_user)
segments << authorized_to_user(items) if current_user
def authorized_snippets
Snippet.where(feature_available_projects.or(not_project_related)).public_or_visible_to_user(current_user)
end
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
def authorized_to_user(items)
items.where(
'author_id = :author_id
OR project_id IN (:project_ids)',
author_id: current_user.id,
project_ids: current_user.authorized_projects.select(:id))
def not_project_related
table[:project_id].eq(nil)
end
def table
Snippet.arel_table
end
def by_visibility(items)
......@@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder
items.where(author_id: params[:author].id)
end
def by_project(items)
return items unless params[:project]
items.where(project_id: params[:project].id)
end
def visibility_from_scope
case params[:scope].to_s
when 'are_private'
......
......@@ -1589,8 +1589,11 @@ class Project < ActiveRecord::Base
end
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)
end
end
def deployment_variables
......
......@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
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)
reference = "#{self.class.reference_prefix}#{id}"
......
......@@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy
enable :create_note
enable :upload_file
enable :read_cycle_analytics
enable :read_project_snippet
end
rule { can?(:reporter_access) }.policy do
......
......@@ -11,8 +11,8 @@ module Groups
def execute
return nil unless group_path
if group = Group.find_by_full_path(group_path)
return group
if namespace = namespace_or_group(group_path)
return namespace
end
if group_path.include?('/') && !Group.supports_nested_groups?
......@@ -40,10 +40,14 @@ module Groups
)
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
last_group
end
def namespace_or_group(group_path)
Namespace.find_by_full_path(group_path)
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
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`
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`
```javascript
const users = [ { name: 'Foo' }, { name: 'Bar' } ];
......
......@@ -85,9 +85,9 @@ module API
use :pagination
end
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
......@@ -106,9 +106,9 @@ module API
use :pagination
end
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
......
......@@ -60,7 +60,7 @@ module API
end
post ':id/mark_as_done' do
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
end
......
......@@ -12,7 +12,7 @@ module API
end
delete ':id' do
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
end
......
......@@ -14,23 +14,33 @@ module Banzai
end
def highlight_node(node)
code = node.text
css_classes = 'code highlight js-syntax-highlight'
language = node.attr('lang')
lang = node.attr('lang')
retried = false
if use_rouge?(language)
lexer = lexer_for(language)
if use_rouge?(lang)
lexer = lexer_for(lang)
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
code = Rouge::Formatters::HTMLGitlab.format(lex(lexer, code), tag: language)
css_classes << " #{language}"
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc.
language = nil
lexer = Rouge::Lexers::PlainText.new
retried = true
language = nil
end
retry
end
highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
......
......@@ -28,9 +28,9 @@ module Gitlab
# encode and clean the bad chars
message.replace clean(message)
rescue ArgumentError
return nil
rescue
rescue ArgumentError => e
return unless e.message.include?('unknown encoding name')
encoding = detect ? detect[:encoding] : "unknown"
"--broken encoding: #{encoding}"
end
......
......@@ -115,7 +115,7 @@ namespace :gemojione do
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
SpriteFactory.cssurl = "image-url('$IMAGE')"
......
......@@ -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
```
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).
### 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/
......@@ -39,6 +39,14 @@ module QA
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
using_wait_time 0 do
set_initial_password_if_present
......@@ -51,7 +59,7 @@ module QA
end
end
def sign_in_using_credentials
def sign_in_using_gitlab_credentials
using_wait_time 0 do
set_initial_password_if_present
......
......@@ -16,6 +16,28 @@ module QA
def personal_access_token
ENV['PERSONAL_ACCESS_TOKEN']
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
......@@ -16,7 +16,7 @@ module QA
end
def sandbox_name
'gitlab-qa-sandbox'
Runtime::Env.sandbox_name || 'gitlab-qa-sandbox'
end
end
end
......
......@@ -4,11 +4,15 @@ module QA
extend self
def name
ENV['GITLAB_USERNAME'] || 'root'
Runtime::Env.user_username || 'root'
end
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
......
......@@ -15,6 +15,14 @@ module QA
@tags = %w[qa test]
end
def network
shell "docker network inspect #{@network}"
rescue CommandError
'bridge'
else
@network
end
def pull
shell "docker pull #{@image}"
end
......@@ -22,7 +30,7 @@ module QA
def register!
shell <<~CMD.tr("\n", ' ')
docker run -d --rm --entrypoint=/bin/sh
--network #{@network} --name #{@name}
--network #{network} --name #{@name}
-e CI_SERVER_URL=#{@address}
-e REGISTER_NON_INTERACTIVE=true
-e REGISTRATION_TOKEN=#{@token}
......
......@@ -3,6 +3,8 @@ require 'open3'
module QA
module Service
module Shellout
CommandError = Class.new(StandardError)
##
# TODO, make it possible to use generic QA framework classes
# as a library - gitlab-org/gitlab-qa#94
......@@ -14,7 +16,7 @@ module QA
out.each { |line| puts line }
if wait.value.exited? && wait.value.exitstatus.nonzero?
raise "Command `#{command}` failed!"
raise CommandError, "Command `#{command}` failed!"
end
end
end
......
......@@ -55,4 +55,25 @@ describe QA::Runtime::Env do
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
......@@ -222,7 +222,7 @@ describe Import::BitbucketController do
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(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
......@@ -240,7 +240,7 @@ describe Import::BitbucketController do
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' }
it 'takes the selected namespace and name' do
......@@ -271,10 +271,14 @@ describe Import::BitbucketController do
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!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator)
.to receive(:new).with(bitbucket_repo, test_name, kind_of(Namespace), user, access_params)
......
......@@ -195,7 +195,7 @@ describe Import::GitlabController do
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(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
......@@ -212,7 +212,7 @@ describe Import::GitlabController do
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' }
it 'takes the selected namespace and name' do
......@@ -243,10 +243,14 @@ describe Import::GitlabController do
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!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator)
.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'
describe SnippetsFinder do
let(:user) { create :user }
let(:user1) { create :user }
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
include Gitlab::Allowable
using RSpec::Parameterized::TableSyntax
context 'filter by visibility' do
let!(:snippet1) { create(:personal_snippet, :private) }
......@@ -67,6 +18,7 @@ describe SnippetsFinder do
end
context 'filter by scope' do
let(:user) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) }
......@@ -84,7 +36,7 @@ describe SnippetsFinder do
expect(snippets).not_to include(snippet2, snippet3)
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
expect(snippets).to include(snippet2)
......@@ -100,6 +52,8 @@ describe SnippetsFinder do
end
context 'filter by author' do
let(:user) { create :user }
let(:user1) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) }
......@@ -147,6 +101,10 @@ describe SnippetsFinder do
end
context 'filter by project' do
let(:user) { create :user }
let(:group) { create :group, :public }
let(:project1) { create(:project, :public, group: group) }
before do
@snippet1 = create(:project_snippet, :private, project: project1)
@snippet2 = create(:project_snippet, :internal, project: project1)
......@@ -203,4 +161,9 @@ describe SnippetsFinder do
expect(snippets).to include(@snippet1)
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
import Vue from 'vue';
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch';
const createComponent = () => {
const Component = Vue.extend(missingBranchComponent);
const mr = {
sourceBranchRemoved: true,
};
return new Component({
el: document.createElement('div'),
propsData: { mr },
});
};
import missingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetMissingBranch', () => {
describe('props', () => {
it('should have props', () => {
const mrProp = missingBranchComponent.props.mr;
let vm;
expect(mrProp.type instanceof Object).toBeTruthy();
expect(mrProp.required).toBeTruthy();
});
beforeEach(() => {
const Component = Vue.extend(missingBranchComponent);
vm = mountComponent(Component, { mr: { sourceBranchRemoved: true } });
});
describe('components', () => {
it('should have components added', () => {
expect(missingBranchComponent.components['mr-widget-merge-help']).toBeDefined();
});
afterEach(() => {
vm.$destroy();
});
describe('computed', () => {
describe('missingBranchName', () => {
it('should return proper branch name', () => {
const vm = createComponent();
expect(vm.missingBranchName).toEqual('source');
vm.mr.sourceBranchRemoved = false;
......@@ -43,7 +27,7 @@ describe('MRWidgetMissingBranch', () => {
describe('template', () => {
it('should have correct elements', () => {
const el = createComponent().$el;
const el = vm.$el;
const content = el.textContent.replace(/\n(\s)+/g, ' ').trim();
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'
describe Banzai::Filter::SyntaxHighlightFilter do
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
it "highlights as plaintext" do
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>')
end
include_examples "XSS prevention", ""
end
context "when a valid language is specified" do
it "highlights as that language" do
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>')
end
include_examples "XSS prevention", "ruby"
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
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>')
end
include_examples "XSS prevention", "gnuplot"
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
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
it "highlights as plaintext" do
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
include_examples "XSS prevention", "ruby"
end
end
......@@ -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]`.',
"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 "
],
[
'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|
it description do
......
......@@ -1590,7 +1590,7 @@ describe Ci::Build do
context 'when the branch is protected' 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
it { is_expected.to include(protected_variable) }
......@@ -1598,7 +1598,7 @@ describe Ci::Build do
context 'when the tag is protected' 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
it { is_expected.to include(protected_variable) }
......@@ -1635,7 +1635,7 @@ describe Ci::Build do
context 'when the branch is protected' 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
it { is_expected.to include(protected_variable) }
......@@ -1643,7 +1643,7 @@ describe Ci::Build do
context 'when the tag is protected' 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
it { is_expected.to include(protected_variable) }
......
......@@ -549,7 +549,7 @@ describe Group do
context 'when the ref is a protected branch' do
before do
create(:protected_branch, name: 'ref', project: project)
allow(project).to receive(:protected_for?).with('ref').and_return(true)
end
it_behaves_like 'ref is protected'
......@@ -557,7 +557,7 @@ describe Group do
context 'when the ref is a protected tag' do
before do
create(:protected_tag, name: 'ref', project: project)
allow(project).to receive(:protected_for?).with('ref').and_return(true)
end
it_behaves_like 'ref is protected'
......@@ -571,6 +571,10 @@ describe Group do
let(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
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
expected_array1 = [protected_variable, secret_variable]
expected_array2 = [variable_child, variable_child_2, variable_child_3]
......
......@@ -2092,7 +2092,7 @@ describe Project do
context 'when the ref is a protected branch' do
before do
create(:protected_branch, name: 'ref', project: project)
allow(project).to receive(:protected_for?).with('ref').and_return(true)
end
it_behaves_like 'ref is protected'
......@@ -2100,7 +2100,7 @@ describe Project do
context 'when the ref is a protected tag' do
before do
create(:protected_tag, name: 'ref', project: project)
allow(project).to receive(:protected_for?).with('ref').and_return(true)
end
it_behaves_like 'ref is protected'
......@@ -2125,6 +2125,8 @@ describe Project do
context 'when the ref is a protected branch' 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)
end
......@@ -2135,6 +2137,8 @@ describe Project do
context 'when the ref is a protected tag' 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)
end
......
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
......
require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
......
......@@ -180,6 +180,18 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
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
......@@ -286,6 +298,14 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/commits'
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
before do
get api("/projects/#{repo_project.id}/-/search", user), scope: 'blobs', search: 'monitors'
......
......@@ -32,6 +32,27 @@ describe API::Snippets do
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
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
describe 'GET /snippets/public' do
......
......@@ -129,6 +129,12 @@ describe API::Todos do
post api("/todos/#{pending_1.id}/mark_as_done", john_doe)
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
......
......@@ -38,6 +38,12 @@ describe API::V3::Todos do
delete v3_api("/todos/#{pending_1.id}", john_doe)
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
......
......@@ -2,7 +2,7 @@ require 'spec_helper'
describe Search::SnippetService do
let(:author) { create(:author) }
let(:project) { create(:project) }
let(:project) { create(:project, :public) }
let!(:public_snippet) { create(:snippet, :public, 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
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(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
......@@ -274,7 +274,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
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' }
it 'takes the selected namespace and name' do
......@@ -305,10 +305,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do
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!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
before do
parent_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do
expect(Gitlab::LegacyGithubImport::ProjectCreator)
.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
expect { post :create, { target_namespace: 'foo/foobar/bar', new_name: test_name, format: :json } }
.to change { Namespace.count }.by(2)
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
context 'when user can not create projects in the chosen namespace' do
......
RSpec.shared_examples 'snippet visibility' do
let!(:author) { create(:user) }
let!(:member) { create(:user) }
let!(:external) { create(:user, :external) }
let!(:snippet_type_visibilities) do
{
public: Snippet::PUBLIC,
internal: Snippet::INTERNAL,
private: Snippet::PRIVATE
}
end
context "For project snippets" do
let!(:users) do
{
unauthenticated: nil,
external: external,
non_member: create(:user),
member: member,
author: author
}
end
let!(:project_type_visibilities) do
{
public: Gitlab::VisibilityLevel::PUBLIC,
internal: Gitlab::VisibilityLevel::INTERNAL,
private: Gitlab::VisibilityLevel::PRIVATE
}
end
let(:project_feature_visibilities) do
{
enabled: ProjectFeature::ENABLED,
private: ProjectFeature::PRIVATE,
disabled: ProjectFeature::DISABLED
}
end
where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
[
# Public projects
[:public, :enabled, :unauthenticated, :public, true],
[:public, :enabled, :unauthenticated, :internal, false],
[:public, :enabled, :unauthenticated, :private, false],
[:public, :enabled, :external, :public, true],
[:public, :enabled, :external, :internal, false],
[:public, :enabled, :external, :private, false],
[:public, :enabled, :non_member, :public, true],
[:public, :enabled, :non_member, :internal, true],
[:public, :enabled, :non_member, :private, false],
[:public, :enabled, :member, :public, true],
[:public, :enabled, :member, :internal, true],
[:public, :enabled, :member, :private, true],
[:public, :enabled, :author, :public, true],
[:public, :enabled, :author, :internal, true],
[:public, :enabled, :author, :private, true],
[:public, :private, :unauthenticated, :public, false],
[:public, :private, :unauthenticated, :internal, false],
[:public, :private, :unauthenticated, :private, false],
[:public, :private, :external, :public, false],
[:public, :private, :external, :internal, false],
[:public, :private, :external, :private, false],
[:public, :private, :non_member, :public, false],
[:public, :private, :non_member, :internal, false],
[:public, :private, :non_member, :private, false],
[:public, :private, :member, :public, true],
[:public, :private, :member, :internal, true],
[:public, :private, :member, :private, true],
[:public, :private, :author, :public, true],
[:public, :private, :author, :internal, true],
[:public, :private, :author, :private, true],
[:public, :disabled, :unauthenticated, :public, false],
[:public, :disabled, :unauthenticated, :internal, false],
[:public, :disabled, :unauthenticated, :private, false],
[:public, :disabled, :external, :public, false],
[:public, :disabled, :external, :internal, false],
[:public, :disabled, :external, :private, false],
[:public, :disabled, :non_member, :public, false],
[:public, :disabled, :non_member, :internal, false],
[:public, :disabled, :non_member, :private, false],
[:public, :disabled, :member, :public, false],
[:public, :disabled, :member, :internal, false],
[:public, :disabled, :member, :private, false],
[:public, :disabled, :author, :public, false],
[:public, :disabled, :author, :internal, false],
[:public, :disabled, :author, :private, false],
# Internal projects
[:internal, :enabled, :unauthenticated, :public, false],
[:internal, :enabled, :unauthenticated, :internal, false],
[:internal, :enabled, :unauthenticated, :private, false],
[:internal, :enabled, :external, :public, false],
[:internal, :enabled, :external, :internal, false],
[:internal, :enabled, :external, :private, false],
[:internal, :enabled, :non_member, :public, true],
[:internal, :enabled, :non_member, :internal, true],
[:internal, :enabled, :non_member, :private, false],
[:internal, :enabled, :member, :public, true],
[:internal, :enabled, :member, :internal, true],
[:internal, :enabled, :member, :private, true],
[:internal, :enabled, :author, :public, true],
[:internal, :enabled, :author, :internal, true],
[:internal, :enabled, :author, :private, true],
[:internal, :private, :unauthenticated, :public, false],
[:internal, :private, :unauthenticated, :internal, false],
[:internal, :private, :unauthenticated, :private, false],
[:internal, :private, :external, :public, false],
[:internal, :private, :external, :internal, false],
[:internal, :private, :external, :private, false],
[:internal, :private, :non_member, :public, false],
[:internal, :private, :non_member, :internal, false],
[:internal, :private, :non_member, :private, false],
[:internal, :private, :member, :public, true],
[:internal, :private, :member, :internal, true],
[:internal, :private, :member, :private, true],
[:internal, :private, :author, :public, true],
[:internal, :private, :author, :internal, true],
[:internal, :private, :author, :private, true],
[:internal, :disabled, :unauthenticated, :public, false],
[:internal, :disabled, :unauthenticated, :internal, false],
[:internal, :disabled, :unauthenticated, :private, false],
[:internal, :disabled, :external, :public, false],
[:internal, :disabled, :external, :internal, false],
[:internal, :disabled, :external, :private, false],
[:internal, :disabled, :non_member, :public, false],
[:internal, :disabled, :non_member, :internal, false],
[:internal, :disabled, :non_member, :private, false],
[:internal, :disabled, :member, :public, false],
[:internal, :disabled, :member, :internal, false],
[:internal, :disabled, :member, :private, false],
[:internal, :disabled, :author, :public, false],
[:internal, :disabled, :author, :internal, false],
[:internal, :disabled, :author, :private, false],
# Private projects
[:private, :enabled, :unauthenticated, :public, false],
[:private, :enabled, :unauthenticated, :internal, false],
[:private, :enabled, :unauthenticated, :private, false],
[:private, :enabled, :external, :public, true],
[:private, :enabled, :external, :internal, true],
[:private, :enabled, :external, :private, true],
[:private, :enabled, :non_member, :public, false],
[:private, :enabled, :non_member, :internal, false],
[:private, :enabled, :non_member, :private, false],
[:private, :enabled, :member, :public, true],
[:private, :enabled, :member, :internal, true],
[:private, :enabled, :member, :private, true],
[:private, :enabled, :author, :public, true],
[:private, :enabled, :author, :internal, true],
[:private, :enabled, :author, :private, true],
[:private, :private, :unauthenticated, :public, false],
[:private, :private, :unauthenticated, :internal, false],
[:private, :private, :unauthenticated, :private, false],
[:private, :private, :external, :public, true],
[:private, :private, :external, :internal, true],
[:private, :private, :external, :private, true],
[:private, :private, :non_member, :public, false],
[:private, :private, :non_member, :internal, false],
[:private, :private, :non_member, :private, false],
[:private, :private, :member, :public, true],
[:private, :private, :member, :internal, true],
[:private, :private, :member, :private, true],
[:private, :private, :author, :public, true],
[:private, :private, :author, :internal, true],
[:private, :private, :author, :private, true],
[:private, :disabled, :unauthenticated, :public, false],
[:private, :disabled, :unauthenticated, :internal, false],
[:private, :disabled, :unauthenticated, :private, false],
[:private, :disabled, :external, :public, false],
[:private, :disabled, :external, :internal, false],
[:private, :disabled, :external, :private, false],
[:private, :disabled, :non_member, :public, false],
[:private, :disabled, :non_member, :internal, false],
[:private, :disabled, :non_member, :private, false],
[:private, :disabled, :member, :public, false],
[:private, :disabled, :member, :internal, false],
[:private, :disabled, :member, :private, false],
[:private, :disabled, :author, :public, false],
[:private, :disabled, :author, :internal, false],
[:private, :disabled, :author, :private, false]
]
end
with_them do
let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) }
let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) }
let!(:user) { users[user_type] }
let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) }
let!(:members) do
project.add_developer(author)
project.add_developer(member)
project.add_developer(external) if project.private?
end
context "For #{params[:project_type]} project and #{params[:user_type]} users" do
it 'should agree with the read_project_snippet policy' do
expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
end
it 'should return proper outcome' do
results = described_class.new(user, project: project).execute
expect(results.include?(snippet)).to eq(outcome)
end
end
context "Without a given project and #{params[:user_type]} users" do
it 'should return proper outcome' do
results = described_class.new(user).execute
expect(results.include?(snippet)).to eq(outcome)
end
end
end
end
context 'For personal snippets' do
let!(:users) do
{
unauthenticated: nil,
external: external,
non_member: create(:user),
author: author
}
end
where(:snippet_visibility, :user_type, :outcome) do
[
[:public, :unauthenticated, true],
[:public, :external, true],
[:public, :non_member, true],
[:public, :author, true],
[:internal, :unauthenticated, false],
[:internal, :external, false],
[:internal, :non_member, true],
[:internal, :author, true],
[:private, :unauthenticated, false],
[:private, :external, false],
[:private, :non_member, false],
[:private, :author, true]
]
end
with_them do
let!(:user) { users[user_type] }
let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) }
context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
it 'should agree with read_personal_snippet policy' do
expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
end
it 'should return proper outcome' do
results = described_class.new(user).execute
expect(results.include?(snippet)).to eq(outcome)
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment