Skip to content
Snippets Groups Projects
Commit 69944ffb authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 1b7381e9
No related branches found
No related tags found
No related merge requests found
Showing
with 469 additions and 238 deletions
Loading
Loading
@@ -756,6 +756,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `state` | TodoStateEnum! | State of the todo |
| `createdAt` | Time! | Timestamp this todo was created |
 
### TodoMarkDonePayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `todo` | Todo! | The requested todo |
### ToggleAwardEmojiPayload
 
| Name | Type | Description |
Loading
Loading
Loading
Loading
@@ -176,7 +176,7 @@ module Gitlab
self.table_name = 'projects'
 
def self.find_by_full_path(path)
order_sql = "(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
where_full_path_in(path).reorder(order_sql).take
end
 
Loading
Loading
Loading
Loading
@@ -28,7 +28,7 @@ dast_environment_deploy:
variables:
- $CI_DEFAULT_BRANCH != $CI_COMMIT_REF_NAME
- $DAST_DISABLED || $DAST_DISABLED_FOR_DEFAULT_BRANCH
- $DAST_WEBSITE # we don't need to create a review app if a URL is already given
- $DAST_WEBSITE # we don't need to create a review app if a URL is already given
 
stop_dast_environment:
extends: .auto-deploy
Loading
Loading
Loading
Loading
@@ -955,7 +955,7 @@ into similar problems in the future (e.g. when new tables are created).
table_name = model_class.quoted_table_name
 
model_class.each_batch(of: batch_size) do |relation|
start_id, end_id = relation.pluck("MIN(#{table_name}.id), MAX(#{table_name}.id)").first
start_id, end_id = relation.pluck("MIN(#{table_name}.id)", "MAX(#{table_name}.id)").first
 
if jobs.length >= BACKGROUND_MIGRATION_JOB_BUFFER_SIZE
# Note: This code path generally only helps with many millions of rows
Loading
Loading
Loading
Loading
@@ -20349,6 +20349,9 @@ msgstr ""
msgid "failed to dismiss associated finding(id=%{finding_id}): %{message}"
msgstr ""
 
msgid "finding is not found or is already attached to a vulnerability"
msgstr ""
msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -90,16 +90,18 @@ describe ApplicationController do
let(:format) { :html }
 
it_behaves_like 'setting gon variables'
end
 
context 'with json format' do
let(:format) { :json }
context 'for peek requests' do
before do
request.path = '/-/peek'
end
 
it_behaves_like 'not setting gon variables'
it_behaves_like 'not setting gon variables'
end
end
 
context 'with atom format' do
let(:format) { :atom }
context 'with json format' do
let(:format) { :json }
 
it_behaves_like 'not setting gon variables'
end
Loading
Loading
Loading
Loading
@@ -228,10 +228,10 @@ describe UploadsController do
user.block
end
 
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
@@ -320,10 +320,10 @@ describe UploadsController do
end
 
context "when not signed in" do
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user)
end
 
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
@@ -439,10 +439,10 @@ describe UploadsController do
user.block
end
 
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
@@ -526,10 +526,10 @@ describe UploadsController do
end
 
context "when not signed in" do
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user)
end
 
it "responds with status 401" do
it "redirects to the sign in page" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
 
expect(response).to have_gitlab_http_status(401)
expect(response).to redirect_to(new_user_session_path)
end
end
 
Loading
Loading
Loading
Loading
@@ -62,13 +62,6 @@ describe 'Editing file blob', :js do
expect(page).to have_content 'NextFeature'
end
 
it 'renders a URL in the content of file as a link' do
project.repository.create_file(user, 'file.yml', '# go to https://gitlab.com', message: 'testing', branch_name: branch)
visit project_edit_blob_path(project, tree_join(branch, 'file.yml'))
expect(page).to have_selector('.ace_content .ace_line a')
end
context 'from blob file path' do
before do
visit project_blob_path(project, tree_join(branch, file_path))
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::Todos::MarkDone do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }) }
describe '#resolve' do
it 'marks a single todo as done' do
result = mark_done_mutation(todo1)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
todo = result[:todo]
expect(todo.id).to eq(todo1.id)
expect(todo.state).to eq('done')
end
it 'handles a todo which is already done as expected' do
result = mark_done_mutation(todo2)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
todo = result[:todo]
expect(todo.id).to eq(todo2.id)
expect(todo.state).to eq('done')
end
it 'ignores requests for todos which do not belong to the current user' do
expect { mark_done_mutation(other_user_todo) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
end
it 'ignores invalid GIDs' do
expect { mutation.resolve(id: 'invalid_gid') }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
end
end
def mark_done_mutation(todo)
mutation.resolve(id: global_id_of(todo))
end
def global_id_of(todo)
todo.to_global_id.to_s
end
end
Loading
Loading
@@ -11,13 +11,6 @@ describe('Blob viewer', () => {
 
preloadFixtures('snippets/show.html');
 
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
beforeEach(() => {
mock = new MockAdapter(axios);
 
Loading
Loading
@@ -73,12 +66,19 @@ describe('Blob viewer', () => {
});
 
it('doesnt reload file if already loaded', done => {
const asyncClick = () =>
new Promise(resolve => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
setTimeout(resolve);
});
asyncClick()
.then(() => asyncClick())
.then(() => {
expect(document.querySelector('.blob-viewer[data-type="simple"]').dataset.loaded).toBe(
'true',
);
expect(
document.querySelector('.blob-viewer[data-type="simple"]').getAttribute('data-loaded'),
).toBe('true');
 
done();
})
Loading
Loading
@@ -100,7 +100,9 @@ describe('Blob viewer', () => {
});
 
it('has tooltip when disabled', () => {
expect(copyButton.dataset.title).toBe('Switch to the source to copy the file contents');
expect(copyButton.getAttribute('data-original-title')).toBe(
'Switch to the source to copy the file contents',
);
});
 
it('is blurred when clicked and disabled', () => {
Loading
Loading
@@ -134,7 +136,7 @@ describe('Blob viewer', () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
 
setTimeout(() => {
expect(copyButton.dataset.title).toBe('Copy file contents');
expect(copyButton.getAttribute('data-original-title')).toBe('Copy file contents');
 
done();
});
Loading
Loading
@@ -175,27 +177,4 @@ describe('Blob viewer', () => {
expect(axios.get.calls.count()).toBe(1);
});
});
describe('a URL inside the blob content', () => {
beforeEach(() => {
mock.onGet('http://test.host/snippets/1.json?viewer=simple').reply(200, {
html:
'<div class="js-blob-content"><pre class="code"><code><span class="line" lang="yaml"><span class="c1">To install gitlab-shell you also need a Go compiler version 1.8 or newer. https://golang.org/dl/</span></span></code></pre></div>',
});
});
it('is rendered as a link in simple view', done => {
asyncClick()
.then(() => {
expect(document.querySelector('.blob-viewer[data-type="simple"]').innerHTML).toContain(
'<a href="https://golang.org/dl/">https://golang.org/dl/</a>',
);
done();
})
.catch(() => {
fail();
done();
});
});
});
});
Loading
Loading
@@ -15,7 +15,12 @@ import boardsStore from '~/boards/stores/boards_store';
 
window.Sortable = Sortable;
 
export default function createComponent({ done, listIssueProps = {}, componentProps = {} }) {
export default function createComponent({
done,
listIssueProps = {},
componentProps = {},
listProps = {},
}) {
const el = document.createElement('div');
 
document.body.appendChild(el);
Loading
Loading
@@ -25,7 +30,7 @@ export default function createComponent({ done, listIssueProps = {}, componentPr
boardsStore.create();
 
const BoardListComp = Vue.extend(BoardList);
const list = new List(listObj);
const list = new List({ ...listObj, ...listProps });
const issue = new ListIssue({
title: 'Testing',
id: 1,
Loading
Loading
@@ -35,7 +40,9 @@ export default function createComponent({ done, listIssueProps = {}, componentPr
assignees: [],
...listIssueProps,
});
list.issuesSize = 1;
if (!Object.prototype.hasOwnProperty.call(listProps, 'issuesSize')) {
list.issuesSize = 1;
}
list.issues.push(issue);
 
const component = new BoardListComp({
Loading
Loading
/* global List */
import Vue from 'vue';
import eventHub from '~/boards/eventhub';
import createComponent from './board_list_common_spec';
import waitForPromises from '../helpers/wait_for_promises';
import '~/boards/models/list';
 
describe('Board list component', () => {
let mock;
let component;
let getIssues;
function generateIssues(compWrapper) {
for (let i = 1; i < 20; i += 1) {
const issue = Object.assign({}, compWrapper.list.issues[0]);
issue.id += i;
compWrapper.list.issues.push(issue);
}
}
 
beforeEach(done => {
({ mock, component } = createComponent({ done }));
});
describe('When Expanded', () => {
beforeEach(done => {
getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {}));
({ mock, component } = createComponent({ done }));
});
 
afterEach(() => {
mock.restore();
});
afterEach(() => {
mock.restore();
component.$destroy();
});
 
it('renders component', () => {
expect(component.$el.classList.contains('board-list-component')).toBe(true);
});
it('loads first page of issues', done => {
waitForPromises()
.then(() => {
expect(getIssues).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
 
it('renders loading icon', done => {
component.loading = true;
it('renders component', () => {
expect(component.$el.classList.contains('board-list-component')).toBe(true);
});
it('renders loading icon', done => {
component.loading = true;
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-loading')).not.toBeNull();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-loading')).not.toBeNull();
 
done();
done();
});
});
});
 
it('renders issues', () => {
expect(component.$el.querySelectorAll('.board-card').length).toBe(1);
});
it('renders issues', () => {
expect(component.$el.querySelectorAll('.board-card').length).toBe(1);
});
 
it('sets data attribute with issue id', () => {
expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1');
});
it('sets data attribute with issue id', () => {
expect(component.$el.querySelector('.board-card').getAttribute('data-issue-id')).toBe('1');
});
 
it('shows new issue form', done => {
component.toggleForm();
it('shows new issue form', done => {
component.toggleForm();
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
 
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
 
done();
done();
});
});
});
 
it('shows new issue form after eventhub event', done => {
eventHub.$emit(`hide-issue-form-${component.list.id}`);
it('shows new issue form after eventhub event', done => {
eventHub.$emit(`hide-issue-form-${component.list.id}`);
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).not.toBeNull();
 
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
expect(component.$el.querySelector('.is-smaller')).not.toBeNull();
 
done();
done();
});
});
});
 
it('does not show new issue form for closed list', done => {
component.list.type = 'closed';
component.toggleForm();
it('does not show new issue form for closed list', done => {
component.list.type = 'closed';
component.toggleForm();
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).toBeNull();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-new-issue-form')).toBeNull();
 
done();
done();
});
});
});
 
it('shows count list item', done => {
component.showCount = true;
it('shows count list item', done => {
component.showCount = true;
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count')).not.toBeNull();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count')).not.toBeNull();
 
expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
'Showing all issues',
);
expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
'Showing all issues',
);
 
done();
done();
});
});
});
 
it('sets data attribute with invalid id', done => {
component.showCount = true;
it('sets data attribute with invalid id', done => {
component.showCount = true;
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe(
'-1',
);
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count').getAttribute('data-issue-id')).toBe(
'-1',
);
 
done();
done();
});
});
});
 
it('shows how many more issues to load', done => {
component.showCount = true;
component.list.issuesSize = 20;
it('shows how many more issues to load', done => {
component.showCount = true;
component.list.issuesSize = 20;
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
'Showing 1 of 20 issues',
);
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count').textContent.trim()).toBe(
'Showing 1 of 20 issues',
);
 
done();
done();
});
});
});
it('loads more issues after scrolling', done => {
spyOn(component.list, 'nextPage');
component.$refs.list.style.height = '100px';
component.$refs.list.style.overflow = 'scroll';
 
for (let i = 1; i < 20; i += 1) {
const issue = Object.assign({}, component.list.issues[0]);
issue.id += i;
component.list.issues.push(issue);
}
it('loads more issues after scrolling', done => {
spyOn(component.list, 'nextPage');
component.$refs.list.style.height = '100px';
component.$refs.list.style.overflow = 'scroll';
generateIssues(component);
Vue.nextTick(() => {
component.$refs.list.scrollTop = 20000;
waitForPromises()
.then(() => {
expect(component.list.nextPage).toHaveBeenCalled();
})
.then(done)
.catch(done.fail);
});
});
 
Vue.nextTick(() => {
component.$refs.list.scrollTop = 20000;
it('does not load issues if already loading', done => {
component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(
new Promise(() => {}),
);
 
setTimeout(() => {
expect(component.list.nextPage).toHaveBeenCalled();
component.onScroll();
component.onScroll();
 
done();
});
waitForPromises()
.then(() => {
expect(component.list.nextPage).toHaveBeenCalledTimes(1);
})
.then(done)
.catch(done.fail);
});
});
 
it('does not load issues if already loading', () => {
component.list.nextPage = spyOn(component.list, 'nextPage').and.returnValue(
new Promise(() => {}),
);
it('shows loading more spinner', done => {
component.showCount = true;
component.list.loadingMore = true;
 
component.onScroll();
component.onScroll();
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
 
expect(component.list.nextPage).toHaveBeenCalledTimes(1);
done();
});
});
});
 
it('shows loading more spinner', done => {
component.showCount = true;
component.list.loadingMore = true;
describe('When Collapsed', () => {
beforeEach(done => {
getIssues = spyOn(List.prototype, 'getIssues').and.returnValue(new Promise(() => {}));
({ mock, component } = createComponent({
done,
listProps: { type: 'closed', collapsed: true, issuesSize: 50 },
}));
generateIssues(component);
component.scrollHeight = spyOn(component, 'scrollHeight').and.returnValue(0);
});
 
Vue.nextTick(() => {
expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
afterEach(() => {
mock.restore();
component.$destroy();
});
 
done();
it('does not load all issues', done => {
waitForPromises()
.then(() => {
// Initial getIssues from list constructor
expect(getIssues).toHaveBeenCalledTimes(1);
})
.then(done)
.catch(done.fail);
});
});
});
Loading
Loading
@@ -7,44 +7,6 @@ describe Redactable do
stub_commonmark_sourcepos_disabled
end
 
shared_examples 'model with redactable field' do
it 'redacts unsubscribe token' do
model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
model.save!
expect(model[field]).to eq 'some text /sent_notifications/REDACTED/unsubscribe more text'
end
it 'ignores not hexadecimal tokens' do
text = 'some text /sent_notifications/token/unsubscribe more text'
model[field] = text
model.save!
expect(model[field]).to eq text
end
it 'ignores not matching texts' do
text = 'some text /sent_notifications/.*/unsubscribe more text'
model[field] = text
model.save!
expect(model[field]).to eq text
end
it 'redacts the field when saving the model before creating markdown cache' do
model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
model.save!
expected = 'some text /sent_notifications/REDACTED/unsubscribe more text'
expect(model[field]).to eq expected
expect(model["#{field}_html"]).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
context 'when model is an issue' do
it_behaves_like 'model with redactable field' do
let(:model) { create(:issue) }
Loading
Loading
Loading
Loading
@@ -2177,6 +2177,50 @@ describe MergeRequest do
end
end
 
describe '#check_mergeability' do
let(:mergeability_service) { double }
before do
allow(MergeRequests::MergeabilityCheckService).to receive(:new) do
mergeability_service
end
end
context 'if the merge status is unchecked' do
before do
subject.mark_as_unchecked!
end
it 'executes MergeabilityCheckService' do
expect(mergeability_service).to receive(:execute)
subject.check_mergeability
end
end
context 'if the merge status is checked' do
context 'and feature flag is enabled' do
it 'executes MergeabilityCheckService' do
expect(mergeability_service).not_to receive(:execute)
subject.check_mergeability
end
end
context 'and feature flag is disabled' do
before do
stub_feature_flags(merge_requests_conditional_mergeability_check: false)
end
it 'does not execute MergeabilityCheckService' do
expect(mergeability_service).to receive(:execute)
subject.check_mergeability
end
end
end
end
describe '#mergeable_state?' do
let(:project) { create(:project, :repository) }
 
Loading
Loading
Loading
Loading
@@ -150,6 +150,19 @@ describe Todo do
end
end
 
describe '#done?' do
let_it_be(:todo1) { create(:todo, state: :pending) }
let_it_be(:todo2) { create(:todo, state: :done) }
it 'returns true for todos with done state' do
expect(todo2.done?).to be_truthy
end
it 'returns false for todos with state pending' do
expect(todo1.done?).to be_falsey
end
end
describe '#self_assigned?' do
let(:user_1) { build(:user) }
 
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe 'Marking todos done' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :pending) }
let(:input) { { id: todo1.to_global_id.to_s } }
let(:mutation) do
graphql_mutation(:todo_mark_done, input,
<<-QL.strip_heredoc
clientMutationId
errors
todo {
id
state
}
QL
)
end
def mutation_response
graphql_mutation_response(:todo_mark_done)
end
it 'marks a single todo as done' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
todo = mutation_response['todo']
expect(todo['id']).to eq(todo1.to_global_id.to_s)
expect(todo['state']).to eq('done')
end
context 'when todo is already marked done' do
let(:input) { { id: todo2.to_global_id.to_s } }
it 'has the expected response' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
todo = mutation_response['todo']
expect(todo['id']).to eq(todo2.to_global_id.to_s)
expect(todo['state']).to eq('done')
end
end
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
errors = json_response['errors']
expect(errors).not_to be_blank
expect(errors.first['message']).to eq(access_error)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
end
end
context 'when using an invalid gid' do
let(:input) { { id: 'invalid_gid' } }
let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab id.' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
errors = json_response['errors']
expect(errors).not_to be_blank
expect(errors.first['message']).to eq(invalid_gid_error)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
expect(other_user_todo.reload.state).to eq('pending')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
# The exact query count will vary depending on the 2FA settings of the
# instance, group, and user. Removing those extra 2FA queries in this case
# may not be a good idea, so we just set up the ideal case.
before do
stub_application_setting(require_two_factor_authentication: true)
login_as(create(:user, :two_factor))
end
# One each for: current user, avatar user, and upload record
it 'only performs three SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(3)
end
end
context 'when logged out' do
# One each for avatar user and upload record
it 'only performs two SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(2)
end
end
end
# frozen_string_literal: true
shared_examples 'model with redactable field' do
it 'redacts unsubscribe token' do
model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
model.save!
expect(model[field]).to eq 'some text /sent_notifications/REDACTED/unsubscribe more text'
end
it 'ignores not hexadecimal tokens' do
text = 'some text /sent_notifications/token/unsubscribe more text'
model[field] = text
model.save!
expect(model[field]).to eq text
end
it 'ignores not matching texts' do
text = 'some text /sent_notifications/.*/unsubscribe more text'
model[field] = text
model.save!
expect(model[field]).to eq text
end
it 'redacts the field when saving the model before creating markdown cache' do
model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text'
model.save!
expected = 'some text /sent_notifications/REDACTED/unsubscribe more text'
expect(model[field]).to eq expected
expect(model["#{field}_html"]).to eq "<p dir=\"auto\">#{expected}</p>"
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment