Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-19 09:09:38 +00:00
parent b852029507
commit 38de2aa494
29 changed files with 465 additions and 176 deletions

View file

@ -1,5 +1,6 @@
<script>
import { mapActions, mapGetters } from 'vuex';
import { cloneDeep } from 'lodash';
import {
GlDropdownItem,
GlDropdownDivider,
@ -37,7 +38,7 @@ export default {
return {
search: '',
participants: [],
selected: this.$store.getters.activeIssue.assignees,
selected: [],
};
},
apollo: {
@ -89,9 +90,20 @@ export default {
isSearchEmpty() {
return this.search === '';
},
currentUser() {
return gon?.current_username;
},
},
created() {
this.selected = cloneDeep(this.activeIssue.assignees);
},
methods: {
...mapActions(['setAssignees']),
async assignSelf() {
const [currentUserObject] = await this.setAssignees(this.currentUser);
this.selectAssignee(currentUserObject);
},
clearSelected() {
this.selected = [];
},
@ -119,7 +131,7 @@ export default {
<template>
<board-editable-item :title="assigneeText" @close="saveAssignees">
<template #collapsed>
<issuable-assignees :users="activeIssue.assignees" />
<issuable-assignees :users="selected" @assign-self="assignSelf" />
</template>
<template #default>

View file

@ -325,11 +325,15 @@ export default {
},
})
.then(({ data }) => {
const { nodes } = data.issueSetAssignees?.issue?.assignees || [];
commit('UPDATE_ISSUE_BY_ID', {
issueId: getters.activeIssue.id,
prop: 'assignees',
value: data.issueSetAssignees.issue.assignees.nodes,
value: nodes,
});
return nodes;
});
},

View file

@ -45,7 +45,7 @@ export default {
</script>
<template>
<div ref="dropdown" class="btn-group ide-nav-dropdown dropdown">
<div ref="dropdown" class="btn-group ide-nav-dropdown dropdown" data-testid="ide-nav-dropdown">
<nav-dropdown-button :show-merge-requests="canReadMergeRequests" />
<div class="dropdown-menu dropdown-menu-left p-0">
<nav-form v-if="isVisibleDropdown" :show-merge-requests="canReadMergeRequests" />

View file

@ -1,9 +1,11 @@
<script>
import { GlButton } from '@gitlab/ui';
import { n__ } from '~/locale';
import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
export default {
components: {
GlButton,
UncollapsedAssigneeList,
},
inject: ['rootPath'],
@ -27,9 +29,15 @@ export default {
<template>
<div class="gl-display-flex gl-flex-direction-column">
<div v-if="emptyUsers" data-testid="none">
<span>
{{ __('None') }}
</span>
<span> {{ __('None') }} -</span>
<gl-button
data-testid="assign-yourself"
category="tertiary"
variant="link"
@click="$emit('assign-self')"
>
<span class="gl-text-gray-400">{{ __('assign yourself') }}</span>
</gl-button>
</div>
<uncollapsed-assignee-list v-else :users="users" :root-path="rootPath" />
</div>

View file

@ -77,7 +77,7 @@ export default {
</script>
<template>
<div>
<div data-testid="image-viewer">
<div :class="innerCssClasses" class="position-relative">
<img ref="contentImg" :src="path" @load="onImgLoad" /> <slot name="image-overlay"></slot>
</div>

View file

@ -143,6 +143,7 @@ export default {
:style="levelIndentation"
class="file-row-name"
data-qa-selector="file_name_content"
data-testid="file-row-name-container"
:class="[fileClasses, { 'str-truncated': !truncateMiddle, 'gl-min-w-0': truncateMiddle }]"
>
<file-icon

View file

@ -1,10 +1,14 @@
<script>
import Pikaday from 'pikaday';
import { GlIcon } from '@gitlab/ui';
import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
export default {
name: 'DatePicker',
components: {
GlIcon,
},
props: {
label: {
type: String,
@ -66,7 +70,7 @@ export default {
<div class="dropdown open">
<button type="button" class="dropdown-menu-toggle" data-toggle="dropdown" @click="toggled">
<span class="dropdown-toggle-text"> {{ label }} </span>
<i class="fa fa-chevron-down" aria-hidden="true"> </i>
<gl-icon name="chevron-down" class="gl-absolute gl-right-3 gl-top-3 gl-text-gray-500" />
</button>
</div>
</div>

View file

@ -14,11 +14,15 @@ module Ci
end
def set_data(model, new_data)
# TODO: Support AWS S3 server side encryption
files.create({
key: key(model),
body: new_data
})
if Feature.enabled?(:ci_live_trace_use_fog_attributes)
files.create(create_attributes(model, new_data))
else
# TODO: Support AWS S3 server side encryption
files.create({
key: key(model),
body: new_data
})
end
end
def append_data(model, new_data, offset)
@ -57,6 +61,13 @@ module Ci
key_raw(model.build_id, model.chunk_index)
end
def create_attributes(model, new_data)
{
key: key(model),
body: new_data
}.merge(object_store_config.fog_attributes)
end
def key_raw(build_id, chunk_index)
"tmp/builds/#{build_id.to_i}/chunks/#{chunk_index.to_i}.log"
end
@ -84,6 +95,14 @@ module Ci
def object_store
Gitlab.config.artifacts.object_store
end
def object_store_raw_config
object_store
end
def object_store_config
@object_store_config ||= ::ObjectStorage::Config.new(object_store_raw_config)
end
end
end
end

View file

@ -3,7 +3,7 @@
.dropdown.inline.gl-ml-3
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } }
= sorted_by
= icon('chevron-down')
= sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sorted_by)

View file

@ -0,0 +1,5 @@
---
title: Migrate chevron-down icon to svg
merge_request: 47591
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Replace fa-chevron-down icon in pikaday
merge_request: 48054
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Support S3 server side encryption in CI cloud native job logs
merge_request: 47536
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add assign self to group boards sidebar
merge_request: 47705
author:
type: added

View file

@ -0,0 +1,8 @@
---
name: ci_live_trace_use_fog_attributes
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47536
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285079
milestone: '13.6'
type: development
group: group::testing
default_enabled: false

View file

@ -228,6 +228,7 @@ with the following properties:
| ---------------------- | -------------------------------------------------------------------------------------- |
| `description` | A description of the code quality violation. |
| `fingerprint` | A unique fingerprint to identify the code quality violation. For example, an MD5 hash. |
| `severity` | A severity string (can be `info`, `minor`, `major`, `critical`, or `blocker`). |
| `location.path` | The relative path to the file containing the code quality violation. |
| `location.lines.begin` | The line on which the code quality violation occurred. |
@ -238,6 +239,7 @@ Example:
{
"description": "'unused' is assigned a value but never used.",
"fingerprint": "7815696ecbf1c96e6894b779456d330e",
"severity": "minor",
"location": {
"path": "lib/index.js",
"lines": {

View file

@ -93,6 +93,11 @@ module ObjectStorage
private
# This returns a Hash of HTTP encryption headers to send along to S3.
#
# They can also be passed in as Fog::AWS::Storage::File attributes, since there
# are aliases defined for them:
# https://github.com/fog/fog-aws/blob/ab288f29a0974d64fd8290db41080e5578be9651/lib/fog/aws/models/storage/file.rb#L24-L25
def aws_server_side_encryption_headers
{
'x-amz-server-side-encryption' => server_side_encryption,

View file

@ -20,6 +20,7 @@ describe('BoardCardAssigneeDropdown', () => {
let fakeApollo;
let getIssueParticipantsSpy;
let getSearchUsersSpy;
let dispatchSpy;
const iid = '111';
const activeIssueName = 'test';
@ -91,10 +92,11 @@ describe('BoardCardAssigneeDropdown', () => {
},
};
jest.spyOn(store, 'dispatch').mockResolvedValue();
dispatchSpy = jest.spyOn(store, 'dispatch').mockResolvedValue();
});
afterEach(() => {
window.gon = {};
jest.restoreAllMocks();
});
@ -305,4 +307,25 @@ describe('BoardCardAssigneeDropdown', () => {
expect(wrapper.find(GlSearchBoxByType).exists()).toBe(true);
});
describe('when assign-self is emitted from IssuableAssignees', () => {
const currentUser = { username: 'self', name: '', id: '' };
beforeEach(() => {
window.gon = { current_username: currentUser.username };
dispatchSpy.mockResolvedValue([currentUser]);
createComponent();
wrapper.find(IssuableAssignees).vm.$emit('assign-self');
});
it('calls setAssignees with currentUser', () => {
expect(store.dispatch).toHaveBeenCalledWith('setAssignees', currentUser.username);
});
it('adds the user to the selected list', async () => {
expect(findByText(currentUser.username).exists()).toBe(true);
});
});
});

View file

@ -13,6 +13,9 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do
clean_frontend_fixtures('blob/balsamiq/')
clean_frontend_fixtures('blob/notebook/')
clean_frontend_fixtures('blob/pdf/')
clean_frontend_fixtures('blob/text/')
clean_frontend_fixtures('blob/binary/')
clean_frontend_fixtures('blob/images/')
end
after do
@ -38,4 +41,16 @@ RSpec.describe 'Raw files', '(JavaScript fixtures)' do
it 'blob/pdf/test.pdf' do
@blob = project.repository.blob_at('e774ebd33', 'files/pdf/test.pdf')
end
it 'blob/text/README.md' do
@blob = project.repository.blob_at('e774ebd33', 'README.md')
end
it 'blob/images/logo-white.png' do
@blob = project.repository.blob_at('e774ebd33', 'files/images/logo-white.png')
end
it 'blob/binary/Gemfile.zip' do
@blob = project.repository.blob_at('e774ebd33', 'Gemfile.zip')
end
end

View file

@ -26,8 +26,8 @@ describe('IssuableAssignees', () => {
createComponent();
});
it('renders "None"', () => {
expect(findEmptyAssignee().text()).toBe('None');
it('renders "None - assign yourself"', () => {
expect(findEmptyAssignee().text()).toBe('None - assign yourself');
});
});
@ -38,4 +38,12 @@ describe('IssuableAssignees', () => {
expect(findUncollapsedAssigneeList().exists()).toBe(true);
});
});
describe('when clicking "assign yourself"', () => {
it('emits "assign-self"', () => {
createComponent();
wrapper.find('[data-testid="assign-yourself"]').vm.$emit('click');
expect(wrapper.emitted('assign-self')).toHaveLength(1);
});
});
});

View file

@ -1,114 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`WebIDE runs 1`] = `
<div>
<article
class="ide position-relative d-flex flex-column align-items-stretch"
>
<div
class="ide-view flex-grow d-flex"
>
<div
class="gl-relative multi-file-commit-panel flex-column"
style="width: 340px;"
>
<div
class="multi-file-commit-panel-inner"
data-testid="ide-side-bar-inner"
>
<div
class="multi-file-loading-container"
>
<div
class="animation-container"
>
<div
class="skeleton-line-1"
/>
<div
class="skeleton-line-2"
/>
<div
class="skeleton-line-3"
/>
</div>
</div>
<div
class="multi-file-loading-container"
>
<div
class="animation-container"
>
<div
class="skeleton-line-1"
/>
<div
class="skeleton-line-2"
/>
<div
class="skeleton-line-3"
/>
</div>
</div>
<div
class="multi-file-loading-container"
>
<div
class="animation-container"
>
<div
class="skeleton-line-1"
/>
<div
class="skeleton-line-2"
/>
<div
class="skeleton-line-3"
/>
</div>
</div>
</div>
<div
class="position-absolute position-top-0 position-bottom-0 drag-handle position-right-0"
size="340"
style="cursor: ew-resize;"
/>
</div>
<div
class="multi-file-edit-pane"
>
<div
class="ide-empty-state"
>
<div
class="row js-empty-state"
>
<div
class="col-12"
>
<div
class="svg-content svg-250"
>
<img
src="/test/empty_state.svg"
/>
</div>
</div>
<div
class="col-12"
>
<div
class="text-content text-center"
>
<h4>
Make and review changes in the browser with the Web IDE
</h4>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
`;

View file

@ -1,4 +1,8 @@
import { TEST_HOST } from 'helpers/test_constants';
import { findAllByText, fireEvent, getByLabelText, screen } from '@testing-library/dom';
import { initIde } from '~/ide';
import extendStore from '~/ide/stores/extend';
import { IDE_DATASET } from './mock_data';
const isFolderRowOpen = row => row.matches('.folder.is-open');
@ -12,16 +16,23 @@ const clickOnLeftSidebarTab = name => {
button.click();
};
const findMonacoEditor = () =>
export const findMonacoEditor = () =>
screen.findByLabelText(/Editor content;/).then(x => x.closest('.monaco-editor'));
const findAndSetEditorValue = async value => {
export const findAndSetEditorValue = async value => {
const editor = await findMonacoEditor();
const uri = editor.getAttribute('data-uri');
window.monaco.editor.getModel(uri).setValue(value);
};
export const getEditorValue = async () => {
const editor = await findMonacoEditor();
const uri = editor.getAttribute('data-uri');
return window.monaco.editor.getModel(uri).getValue();
};
const findTreeBody = () => screen.findByTestId('ide-tree-body', {}, { timeout: 5000 });
const findRootActions = () => screen.findByTestId('ide-root-actions', {}, { timeout: 7000 });
@ -107,6 +118,10 @@ export const createFile = async (path, content) => {
await findAndSetEditorValue(content);
};
export const getFilesList = () => {
return screen.getAllByTestId('file-row-name-container').map(e => e.textContent.trim());
};
export const deleteFile = async path => {
const row = await findAndTraverseToPath(path);
clickFileRowAction(row, 'Delete');
@ -120,3 +135,16 @@ export const commit = async () => {
screen.getByText('Commit').click();
};
export const createIdeComponent = (container, { isRepoEmpty = false, path = '' } = {}) => {
global.jsdom.reconfigure({
url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum${
isRepoEmpty ? '-empty' : ''
}/tree/master/-/${path}`,
});
const el = document.createElement('div');
Object.assign(el.dataset, IDE_DATASET);
container.appendChild(el);
return initIde(el, { extendStore });
};

View file

@ -0,0 +1,12 @@
export const IDE_DATASET = {
emptyStateSvgPath: '/test/empty_state.svg',
noChangesStateSvgPath: '/test/no_changes_state.svg',
committedStateSvgPath: '/test/committed_state.svg',
pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
promotionSvgPath: '/test/promotion.svg',
ciHelpPagePath: '/test/ci_help_page',
webIDEHelpPagePath: '/test/web_ide_help_page',
clientsidePreviewEnabled: 'true',
renderWhitespaceInCode: 'false',
codesandboxBundlerUrl: 'test/codesandbox_bundler',
};

View file

@ -1,61 +1,27 @@
import { TEST_HOST } from 'helpers/test_constants';
import { waitForText } from 'helpers/wait_for_text';
import waitForPromises from 'helpers/wait_for_promises';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import { createCommitId } from 'test_helpers/factories/commit_id';
import { initIde } from '~/ide';
import extendStore from '~/ide/stores/extend';
import * as ideHelper from './ide_helper';
const TEST_DATASET = {
emptyStateSvgPath: '/test/empty_state.svg',
noChangesStateSvgPath: '/test/no_changes_state.svg',
committedStateSvgPath: '/test/committed_state.svg',
pipelinesEmptyStateSvgPath: '/test/pipelines_empty_state.svg',
promotionSvgPath: '/test/promotion.svg',
ciHelpPagePath: '/test/ci_help_page',
webIDEHelpPagePath: '/test/web_ide_help_page',
clientsidePreviewEnabled: 'true',
renderWhitespaceInCode: 'false',
codesandboxBundlerUrl: 'test/codesandbox_bundler',
};
import * as ideHelper from './helpers/ide_helper';
describe('WebIDE', () => {
useOverclockTimers();
let vm;
let root;
let container;
beforeEach(() => {
root = document.createElement('div');
document.body.appendChild(root);
global.jsdom.reconfigure({
url: `${TEST_HOST}/-/ide/project/gitlab-test/lorem-ipsum`,
});
setFixtures('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
});
afterEach(() => {
vm.$destroy();
vm = null;
root.remove();
});
const createComponent = () => {
const el = document.createElement('div');
Object.assign(el.dataset, TEST_DATASET);
root.appendChild(el);
vm = initIde(el, { extendStore });
};
it('runs', () => {
createComponent();
expect(root).toMatchSnapshot();
});
it('user commits changes', async () => {
createComponent();
vm = ideHelper.createIdeComponent(container);
await ideHelper.createFile('foo/bar/test.txt', 'Lorem ipsum dolar sit');
await ideHelper.deleteFile('foo/bar/.gitkeep');
@ -89,7 +55,7 @@ describe('WebIDE', () => {
});
it('user adds file that starts with +', async () => {
createComponent();
vm = ideHelper.createIdeComponent(container);
await ideHelper.createFile('+test', 'Hello world!');
await ideHelper.openFile('+test');

View file

@ -0,0 +1,163 @@
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import { findByText, screen } from '@testing-library/dom';
import * as ideHelper from './helpers/ide_helper';
describe('IDE: User opens IDE', () => {
useOverclockTimers();
let vm;
let container;
beforeEach(() => {
setFixtures('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
});
afterEach(() => {
vm.$destroy();
vm = null;
});
it('shows loading indicator while the IDE is loading', async () => {
vm = ideHelper.createIdeComponent(container);
expect(container.querySelectorAll('.multi-file-loading-container')).toHaveLength(3);
});
describe('when the project is empty', () => {
beforeEach(() => {
vm = ideHelper.createIdeComponent(container, { isRepoEmpty: true });
});
it('shows "No files" in the left sidebar', async () => {
expect(await screen.findByText('No files')).toBeDefined();
});
it('shows a "New file" button', async () => {
const button = await screen.findByTitle('New file');
expect(button.tagName).toEqual('BUTTON');
});
});
describe('when the file tree is loaded', () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container);
await screen.findByText('README'); // wait for file tree to load
});
it('shows a list of files in the left sidebar', async () => {
expect(ideHelper.getFilesList()).toEqual(
expect.arrayContaining(['README', 'LICENSE', 'CONTRIBUTING.md']),
);
});
it('shows empty state in the main editor window', async () => {
expect(
await screen.findByText(
"Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
),
).toBeDefined();
});
it('shows commit button in disabled state', async () => {
const button = await screen.findByTestId('begin-commit-button');
expect(button.getAttribute('disabled')).toBeDefined();
});
it('shows branch/MR dropdown with master selected', async () => {
const dropdown = await screen.findByTestId('ide-nav-dropdown');
expect(dropdown.textContent).toContain('master');
});
});
describe('a path to a text file is present in the URL', () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container, { path: 'README.md' });
// a new tab is open for README.md
await findByText(document.querySelector('.multi-file-edit-pane'), 'README.md');
});
it('opens the file and its contents are shown in Monaco', async () => {
expect(await ideHelper.getEditorValue()).toContain('Sample repo for testing gitlab features');
});
});
describe('a path to a binary file is present in the URL', () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container, { path: 'Gemfile.zip' });
// a new tab is open for Gemfile.zip
await findByText(document.querySelector('.multi-file-edit-pane'), 'Gemfile.zip');
});
it('shows download viewer', async () => {
const downloadButton = await screen.findByText('Download');
expect(downloadButton.getAttribute('download')).toEqual('Gemfile.zip');
expect(downloadButton.getAttribute('href')).toContain('/raw/');
});
});
describe('a path to an image is present in the URL', () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container, { path: 'files/images/logo-white.png' });
// a new tab is open for logo-white.png
await findByText(document.querySelector('.multi-file-edit-pane'), 'logo-white.png');
});
it('shows image viewer', async () => {
const viewer = await screen.findByTestId('image-viewer');
const img = viewer.querySelector('img');
expect(img.src).toContain('logo-white.png');
});
});
describe('path in URL is a directory', () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container, { path: 'files/images' });
// wait for folders in left sidebar to be expanded
await screen.findByText('images');
});
it('expands folders in the left sidebar', () => {
expect(ideHelper.getFilesList()).toEqual(
expect.arrayContaining(['files', 'images', 'logo-white.png', 'logo-black.png']),
);
});
it('shows empty state in the main editor window', async () => {
expect(
await screen.findByText(
"Select a file from the left sidebar to begin editing. Afterwards, you'll be able to commit your changes.",
),
).toBeDefined();
});
});
describe("a file for path in url doesn't exist in the repo", () => {
beforeEach(async () => {
vm = ideHelper.createIdeComponent(container, { path: 'abracadabra/hocus-focus.txt' });
// a new tab is open for hocus-focus.txt
await findByText(document.querySelector('.multi-file-edit-pane'), 'hocus-focus.txt');
});
it('create new folders and file in the left sidebar', () => {
expect(ideHelper.getFilesList()).toEqual(
expect.arrayContaining(['abracadabra', 'hocus-focus.txt']),
);
});
it('creates a blank new file', async () => {
expect(await ideHelper.getEditorValue()).toEqual('\n');
});
});
});

View file

@ -1,10 +1,24 @@
/* eslint-disable global-require, import/no-unresolved */
import { memoize } from 'lodash';
import { readFileSync } from 'fs';
import { join } from 'path';
export const getProject = () => require('test_fixtures/api/projects/get.json');
export const getEmptyProject = () => require('test_fixtures/api/projects/get_empty.json');
export const getBranch = () => require('test_fixtures/api/projects/branches/get.json');
export const getMergeRequests = () => require('test_fixtures/api/merge_requests/get.json');
export const getRepositoryFiles = () => require('test_fixtures/projects_json/files.json');
export const getBlobReadme = () =>
readFileSync(require.resolve('test_fixtures/blob/text/README.md'), 'utf8');
export const getBlobZip = () =>
readFileSync(require.resolve('test_fixtures/blob/binary/Gemfile.zip'), 'utf8');
export const getBlobImage = () =>
readFileSync(
join(require.resolve('test_fixtures/blob/text/README.md'), '../..', 'images/logo-white.png'),
'utf8',
);
export const getPipelinesEmptyResponse = () =>
require('test_fixtures/projects_json/pipelines_empty.json');
export const getCommit = memoize(() => getBranch().commit);

View file

@ -1,5 +1,14 @@
import { Server, Model, RestSerializer } from 'miragejs';
import { getProject, getBranch, getMergeRequests, getRepositoryFiles } from 'test_helpers/fixtures';
import {
getProject,
getEmptyProject,
getBranch,
getMergeRequests,
getRepositoryFiles,
getBlobReadme,
getBlobImage,
getBlobZip,
} from 'test_helpers/fixtures';
import setupRoutes from './routes';
export const createMockServerOptions = () => ({
@ -18,9 +27,23 @@ export const createMockServerOptions = () => ({
seeds(schema) {
schema.db.loadData({
files: getRepositoryFiles().map(path => ({ path })),
projects: [getProject()],
projects: [getProject(), getEmptyProject()],
branches: [getBranch()],
mergeRequests: getMergeRequests(),
filesRaw: [
{
raw: getBlobReadme(),
path: 'README.md',
},
{
raw: getBlobZip(),
path: 'Gemfile.zip',
},
{
raw: getBlobImage(),
path: 'files/images/logo-white.png',
},
],
userPermissions: [
{
createMergeRequestIn: true,

View file

@ -19,6 +19,18 @@ export default server => {
return schema.db.files.map(({ path }) => path);
});
server.get('/:namespace/:project/-/blob/:sha/*path', (schema, request) => {
const { path } = schema.db.files.findBy({ path: request.params.path });
return { path, rawPath: request.url.replace('/-/blob', '/-/raw') };
});
server.get('/:namespace/:project/-/raw/:sha/*path', (schema, request) => {
const { path } = request.params;
return schema.db.filesRaw.findBy({ path })?.raw || 'Sample content';
});
server.post('/api/v4/projects/:id/repository/commits', (schema, request) => {
const { branch: branchName, commit_message: message, actions } = JSON.parse(
request.requestBody,

View file

@ -74,6 +74,52 @@ RSpec.describe Ci::BuildTraceChunks::Fog do
expect(data_store.data(model)).to eq new_data
end
context 'when S3 server side encryption is enabled' do
before do
config = Gitlab.config.artifacts.object_store.to_h
config[:storage_options] = { server_side_encryption: 'AES256' }
allow(data_store).to receive(:object_store_raw_config).and_return(config)
end
it 'creates a file with attributes' do
expect_next_instance_of(Fog::AWS::Storage::Files) do |files|
expect(files).to receive(:create).with(
hash_including(
key: anything,
body: new_data,
'x-amz-server-side-encryption' => 'AES256')
).and_call_original
end
expect(data_store.data(model)).to be_nil
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
context 'when ci_live_trace_use_fog_attributes flag is disabled' do
before do
stub_feature_flags(ci_live_trace_use_fog_attributes: false)
end
it 'does not pass along Fog attributes' do
expect_next_instance_of(Fog::AWS::Storage::Files) do |files|
expect(files).to receive(:create).with(
key: anything,
body: new_data
).and_call_original
end
expect(data_store.data(model)).to be_nil
data_store.set_data(model, new_data)
expect(data_store.data(model)).to eq new_data
end
end
end
end
end

View file

@ -22,6 +22,16 @@ module StubObjectStorage
background_upload: false,
direct_upload: false
)
new_config = config.to_h.deep_symbolize_keys.merge({
enabled: enabled,
proxy_download: proxy_download,
background_upload: background_upload,
direct_upload: direct_upload
})
# Needed for ObjectStorage::Config compatibility
allow(config).to receive(:to_hash).and_return(new_config)
allow(config).to receive(:to_h).and_return(new_config)
allow(config).to receive(:enabled) { enabled }
allow(config).to receive(:proxy_download) { proxy_download }
allow(config).to receive(:background_upload) { background_upload }