summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-12-09 03:07:14 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-12-09 03:07:14 +0000
commit8e7172c40e205a27c204de448ac4fe0551ffa1af (patch)
treec8a1824a1793268daa166e82a2eda5da71a8f15f /spec
parent496e1ea9716836b3d8cb0d556d23692c0e68976b (diff)
downloadgitlab-ce-8e7172c40e205a27c204de448ac4fe0551ffa1af.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/db/schema_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb8
-rw-r--r--spec/fixtures/config/redis_cluster_format_host.yml29
-rw-r--r--spec/frontend/__helpers__/mock_window_location_helper.js9
-rw-r--r--spec/frontend/ide/init_gitlab_web_ide_spec.js66
-rw-r--r--spec/frontend/ide/remote/index_spec.js91
-rw-r--r--spec/frontend/lib/utils/create_and_submit_form_spec.js73
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js3
-rw-r--r--spec/helpers/ide_helper_spec.rb5
-rw-r--r--spec/helpers/nav/top_nav_helper_spec.rb24
-rw-r--r--spec/helpers/wiki_helper_spec.rb2
-rw-r--r--spec/requests/web_ide/remote_ide_controller_spec.rb141
-rw-r--r--spec/routing/web_ide_routing_spec.rb22
-rw-r--r--spec/support/redis/redis_shared_examples.rb55
14 files changed, 502 insertions, 30 deletions
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 66dfc38ad26..61119afae34 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -9,9 +9,7 @@ RSpec.describe 'Database schema' do
let(:tables) { connection.tables }
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
- IGNORED_INDEXES_ON_FKS = {
- issues: %w[work_item_type_id]
- }.with_indifferent_access.freeze
+ IGNORED_INDEXES_ON_FKS = {}.with_indifferent_access.freeze
TABLE_PARTITIONS = %w[ci_builds_metadata].freeze
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index 648dcc9b5df..8073e7e9556 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -459,25 +459,25 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
</a>
</div>
<!---->
- <button type="button" class="btn qa-apply-btn js-apply-btn">Apply suggestion</button>
+ <button type="button" class="btn js-apply-btn">Apply suggestion</button>
</div>
<table class="mb-3 md-suggestion-diff js-syntax-highlight code white">
<tbody>
<tr class="line_holder old">
- <td class="diff-line-num old_line qa-old-diff-line-number old">9</td>
+ <td class="diff-line-num old_line old">9</td>
<td class="diff-line-num new_line old"></td>
<td class="line_content old"><span>Old
</span></td>
</tr>
<tr class="line_holder new">
<td class="diff-line-num old_line new"></td>
- <td class="diff-line-num new_line qa-new-diff-line-number new">9</td>
+ <td class="diff-line-num new_line new">9</td>
<td class="line_content new"><span>New
</span></td>
</tr>
<tr class="line_holder new">
<td class="diff-line-num old_line new"></td>
- <td class="diff-line-num new_line qa-new-diff-line-number new">10</td>
+ <td class="diff-line-num new_line new">10</td>
<td class="line_content new"><span> And newer
</span></td>
</tr>
diff --git a/spec/fixtures/config/redis_cluster_format_host.yml b/spec/fixtures/config/redis_cluster_format_host.yml
new file mode 100644
index 00000000000..7303db72c4e
--- /dev/null
+++ b/spec/fixtures/config/redis_cluster_format_host.yml
@@ -0,0 +1,29 @@
+# redis://[:password@]host[:port][/db-number][?option=value]
+# more details: http://www.iana.org/assignments/uri-schemes/prov/redis
+development:
+ password: myclusterpassword
+ cluster:
+ -
+ host: development-master1
+ port: 6379
+ -
+ host: development-master2
+ port: 6379
+test:
+ password: myclusterpassword
+ cluster:
+ -
+ host: test-master1
+ port: 6379
+ -
+ host: test-master2
+ port: 6379
+production:
+ password: myclusterpassword
+ cluster:
+ -
+ host: production-master1
+ port: 6379
+ -
+ host: production-master2
+ port: 6379
diff --git a/spec/frontend/__helpers__/mock_window_location_helper.js b/spec/frontend/__helpers__/mock_window_location_helper.js
index 14082857053..a923ca661c5 100644
--- a/spec/frontend/__helpers__/mock_window_location_helper.js
+++ b/spec/frontend/__helpers__/mock_window_location_helper.js
@@ -27,12 +27,19 @@ const useMockLocation = (fn) => {
* Create an object with the location interface but `jest.fn()` implementations.
*/
export const createWindowLocationSpy = () => {
- return {
+ const { origin, href } = window.location;
+
+ const mockLocation = {
assign: jest.fn(),
reload: jest.fn(),
replace: jest.fn(),
toString: jest.fn(),
+ origin,
+ // TODO: Do we need to update `origin` if `href` is changed?
+ href,
};
+
+ return mockLocation;
};
/**
diff --git a/spec/frontend/ide/init_gitlab_web_ide_spec.js b/spec/frontend/ide/init_gitlab_web_ide_spec.js
index ae4d8d6d947..3b5b1ede28c 100644
--- a/spec/frontend/ide/init_gitlab_web_ide_spec.js
+++ b/spec/frontend/ide/init_gitlab_web_ide_spec.js
@@ -1,8 +1,13 @@
import { start } from '@gitlab/web-ide';
import { initGitlabWebIDE } from '~/ide/init_gitlab_web_ide';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_action';
+import { createAndSubmitForm } from '~/lib/utils/create_and_submit_form';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('@gitlab/web-ide');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_action');
+jest.mock('~/lib/utils/create_and_submit_form');
const ROOT_ELEMENT_ID = 'ide';
const TEST_NONCE = 'test123nonce';
@@ -10,8 +15,16 @@ const TEST_PROJECT_PATH = 'group1/project1';
const TEST_BRANCH_NAME = '12345-foo-patch';
const TEST_GITLAB_URL = 'https://test-gitlab/';
const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/webpack/assets/gitlab-web-ide/public/path';
+const TEST_IDE_REMOTE_PATH = '/-/ide/remote/:remote_host/:remote_path';
+const TEST_START_REMOTE_PARAMS = {
+ remoteHost: 'dev.example.gitlab.com/test',
+ remotePath: '/test/projects/f oo',
+ connectionToken: '123abc',
+};
describe('ide/init_gitlab_web_ide', () => {
+ let resolveConfirm;
+
const createRootElement = () => {
const el = document.createElement('div');
@@ -21,19 +34,32 @@ describe('ide/init_gitlab_web_ide', () => {
el.dataset.projectPath = TEST_PROJECT_PATH;
el.dataset.cspNonce = TEST_NONCE;
el.dataset.branchName = TEST_BRANCH_NAME;
+ el.dataset.ideRemotePath = TEST_IDE_REMOTE_PATH;
document.body.append(el);
};
const findRootElement = () => document.getElementById(ROOT_ELEMENT_ID);
- const act = () => initGitlabWebIDE(findRootElement());
+ const createSubject = () => initGitlabWebIDE(findRootElement());
+ const triggerHandleStartRemote = (startRemoteParams) => {
+ const [, config] = start.mock.calls[0];
+
+ config.handleStartRemote(startRemoteParams);
+ };
beforeEach(() => {
process.env.GITLAB_WEB_IDE_PUBLIC_PATH = TEST_GITLAB_WEB_IDE_PUBLIC_PATH;
window.gon.gitlab_url = TEST_GITLAB_URL;
+ confirmAction.mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ resolveConfirm = resolve;
+ }),
+ );
+
createRootElement();
- act();
+ createSubject();
});
afterEach(() => {
@@ -48,6 +74,7 @@ describe('ide/init_gitlab_web_ide', () => {
ref: TEST_BRANCH_NAME,
gitlabUrl: TEST_GITLAB_URL,
nonce: TEST_NONCE,
+ handleStartRemote: expect.any(Function),
});
});
@@ -59,4 +86,39 @@ describe('ide/init_gitlab_web_ide', () => {
'<div id="ide" class="gl--flex-center gl-relative gl-h-full"></div>',
);
});
+
+ describe('when handleStartRemote is triggered', () => {
+ beforeEach(() => {
+ triggerHandleStartRemote(TEST_START_REMOTE_PARAMS);
+ });
+
+ it('promts for confirm', () => {
+ expect(confirmAction).toHaveBeenCalledWith(expect.any(String), {
+ primaryBtnText: expect.any(String),
+ cancelBtnText: expect.any(String),
+ });
+ });
+
+ it('does not submit, when not confirmed', async () => {
+ resolveConfirm(false);
+
+ await waitForPromises();
+
+ expect(createAndSubmitForm).not.toHaveBeenCalled();
+ });
+
+ it('submits, when confirmed', async () => {
+ resolveConfirm(true);
+
+ await waitForPromises();
+
+ expect(createAndSubmitForm).toHaveBeenCalledWith({
+ url: '/-/ide/remote/dev.example.gitlab.com%2Ftest/test/projects/f%20oo',
+ data: {
+ connection_token: TEST_START_REMOTE_PARAMS.connectionToken,
+ return_url: window.location.href,
+ },
+ });
+ });
+ });
});
diff --git a/spec/frontend/ide/remote/index_spec.js b/spec/frontend/ide/remote/index_spec.js
new file mode 100644
index 00000000000..0f23b0a4e45
--- /dev/null
+++ b/spec/frontend/ide/remote/index_spec.js
@@ -0,0 +1,91 @@
+import { startRemote } from '@gitlab/web-ide';
+import { getBaseConfig, setupRootElement } from '~/ide/lib/gitlab_web_ide';
+import { mountRemoteIDE } from '~/ide/remote';
+import { TEST_HOST } from 'helpers/test_constants';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+
+jest.mock('@gitlab/web-ide');
+jest.mock('~/ide/lib/gitlab_web_ide');
+
+const TEST_DATA = {
+ remoteHost: 'example.com:3443',
+ remotePath: 'test/path/gitlab',
+ cspNonce: 'just7some8noncense',
+ connectionToken: 'connectAtoken',
+ returnUrl: 'https://example.com/return',
+};
+
+const TEST_BASE_CONFIG = {
+ gitlabUrl: '/test/gitlab',
+};
+
+const TEST_RETURN_URL_SAME_ORIGIN = `${TEST_HOST}/foo/example`;
+
+describe('~/ide/remote/index', () => {
+ useMockLocationHelper();
+ const originalHref = window.location.href;
+
+ let el;
+ let rootEl;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ Object.entries(TEST_DATA).forEach(([key, value]) => {
+ el.dataset[key] = value;
+ });
+
+ // Stub setupRootElement so we can assert on return element
+ rootEl = document.createElement('div');
+ setupRootElement.mockReturnValue(rootEl);
+
+ // Stub getBaseConfig so we can assert
+ getBaseConfig.mockReturnValue(TEST_BASE_CONFIG);
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ mountRemoteIDE(el);
+ });
+
+ it('calls startRemote', () => {
+ expect(startRemote).toHaveBeenCalledWith(rootEl, {
+ ...TEST_BASE_CONFIG,
+ nonce: TEST_DATA.cspNonce,
+ connectionToken: TEST_DATA.connectionToken,
+ remoteAuthority: `/${TEST_DATA.remoteHost}`,
+ hostPath: `/${TEST_DATA.remotePath}`,
+ handleError: expect.any(Function),
+ handleClose: expect.any(Function),
+ });
+ });
+ });
+
+ describe.each`
+ returnUrl | fnName | reloadExpectation | hrefExpectation
+ ${TEST_DATA.returnUrl} | ${'handleError'} | ${1} | ${originalHref}
+ ${TEST_DATA.returnUrl} | ${'handleClose'} | ${1} | ${originalHref}
+ ${TEST_RETURN_URL_SAME_ORIGIN} | ${'handleClose'} | ${0} | ${TEST_RETURN_URL_SAME_ORIGIN}
+ ${TEST_RETURN_URL_SAME_ORIGIN} | ${'handleError'} | ${0} | ${TEST_RETURN_URL_SAME_ORIGIN}
+ ${''} | ${'handleClose'} | ${1} | ${originalHref}
+ `(
+ 'with returnUrl=$returnUrl and fn=$fnName',
+ ({ returnUrl, fnName, reloadExpectation, hrefExpectation }) => {
+ beforeEach(() => {
+ el.dataset.returnUrl = returnUrl;
+
+ mountRemoteIDE(el);
+ });
+
+ it('changes location', () => {
+ expect(window.location.reload).not.toHaveBeenCalled();
+
+ const [, config] = startRemote.mock.calls[0];
+
+ config[fnName]();
+
+ expect(window.location.reload).toHaveBeenCalledTimes(reloadExpectation);
+ expect(window.location.href).toBe(hrefExpectation);
+ });
+ },
+ );
+});
diff --git a/spec/frontend/lib/utils/create_and_submit_form_spec.js b/spec/frontend/lib/utils/create_and_submit_form_spec.js
new file mode 100644
index 00000000000..9f2472c60f7
--- /dev/null
+++ b/spec/frontend/lib/utils/create_and_submit_form_spec.js
@@ -0,0 +1,73 @@
+import csrf from '~/lib/utils/csrf';
+import { TEST_HOST } from 'helpers/test_constants';
+import { createAndSubmitForm } from '~/lib/utils/create_and_submit_form';
+import { joinPaths } from '~/lib/utils/url_utility';
+
+const TEST_URL = '/foo/bar/lorem';
+const TEST_DATA = {
+ 'test_thing[0]': 'Lorem Ipsum',
+ 'test_thing[1]': 'Dolar Sit',
+ x: 123,
+};
+const TEST_CSRF = 'testcsrf00==';
+
+describe('~/lib/utils/create_and_submit_form', () => {
+ let submitSpy;
+
+ const findForm = () => document.querySelector('form');
+ const findInputsModel = () =>
+ Array.from(findForm().querySelectorAll('input')).map((inputEl) => ({
+ type: inputEl.type,
+ name: inputEl.name,
+ value: inputEl.value,
+ }));
+
+ beforeEach(() => {
+ submitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit');
+ document.head.innerHTML = `<meta name="csrf-token" content="${TEST_CSRF}">`;
+ csrf.init();
+ });
+
+ afterEach(() => {
+ document.head.innerHTML = '';
+ document.body.innerHTML = '';
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createAndSubmitForm({
+ url: TEST_URL,
+ data: TEST_DATA,
+ });
+ });
+
+ it('creates form', () => {
+ const form = findForm();
+
+ expect(form.action).toBe(joinPaths(TEST_HOST, TEST_URL));
+ expect(form.method).toBe('post');
+ expect(form.style).toMatchObject({
+ display: 'none',
+ });
+ });
+
+ it('creates inputs', () => {
+ expect(findInputsModel()).toEqual([
+ ...Object.keys(TEST_DATA).map((key) => ({
+ type: 'hidden',
+ name: key,
+ value: String(TEST_DATA[key]),
+ })),
+ {
+ type: 'hidden',
+ name: 'authenticity_token',
+ value: TEST_CSRF,
+ },
+ ]);
+ });
+
+ it('submits form', () => {
+ expect(submitSpy).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
index 8e08864bdb8..cbb5aa52694 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
@@ -232,6 +232,7 @@ describe('Container Expiration Policy Settings Form', () => {
describe('form', () => {
describe('form submit event', () => {
useMockLocationHelper();
+ const originalHref = window.location.href;
it('save has type submit', () => {
mountComponent();
@@ -319,7 +320,7 @@ describe('Container Expiration Policy Settings Form', () => {
await submitForm();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
- expect(window.location.href).toBeUndefined();
+ expect(window.location.href).toBe(originalHref);
});
it('parses the error messages', async () => {
diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb
index 447967fd345..06ea1f40eab 100644
--- a/spec/helpers/ide_helper_spec.rb
+++ b/spec/helpers/ide_helper_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe IdeHelper do
+RSpec.describe IdeHelper, feature_category: :web_ide do
describe '#ide_data' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { project.creator }
@@ -30,7 +30,8 @@ RSpec.describe IdeHelper do
help_page_path('user/project/web_ide/index.md', anchor: 'vscode-reimplementation'),
'branch-name' => 'master',
'project-path' => project.path_with_namespace,
- 'csp-nonce' => 'test-csp-nonce'
+ 'csp-nonce' => 'test-csp-nonce',
+ 'ide-remote-path' => ide_remote_path(remote_host: ':remote_host', remote_path: ':remote_path')
)
end
diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb
index 0d43cfaae90..c4a8536032e 100644
--- a/spec/helpers/nav/top_nav_helper_spec.rb
+++ b/spec/helpers/nav/top_nav_helper_spec.rb
@@ -122,10 +122,10 @@ RSpec.describe Nav::TopNavHelper do
title: 'Switch to'
)
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
- css_class: 'qa-projects-dropdown',
data: {
track_action: 'click_dropdown',
- track_label: 'projects_dropdown'
+ track_label: 'projects_dropdown',
+ qa_selector: 'projects_dropdown'
},
icon: 'project',
id: 'project',
@@ -219,10 +219,10 @@ RSpec.describe Nav::TopNavHelper do
title: 'Switch to'
)
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
- css_class: 'qa-groups-dropdown',
data: {
track_action: 'click_dropdown',
- track_label: 'groups_dropdown'
+ track_label: 'groups_dropdown',
+ qa_selector: 'groups_dropdown'
},
icon: 'group',
id: 'groups',
@@ -323,10 +323,7 @@ RSpec.describe Nav::TopNavHelper do
title: 'Explore'
)
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
- data: {
- qa_selector: 'milestones_link',
- **menu_data_tracking_attrs('milestones')
- },
+ data: { **menu_data_tracking_attrs('milestones') },
href: '/dashboard/milestones',
icon: 'clock',
id: 'milestones',
@@ -385,10 +382,7 @@ RSpec.describe Nav::TopNavHelper do
title: 'Explore'
)
expected_primary = ::Gitlab::Nav::TopNavMenuItem.build(
- data: {
- qa_selector: 'activity_link',
- **menu_data_tracking_attrs('activity')
- },
+ data: { **menu_data_tracking_attrs('activity') },
href: '/dashboard/activity',
icon: 'history',
id: 'activity',
@@ -417,15 +411,13 @@ RSpec.describe Nav::TopNavHelper do
it 'has admin as first :secondary item' do
expected_admin_item = ::Gitlab::Nav::TopNavMenuItem.build(
data: {
- qa_selector: 'menu_item_link',
- qa_title: 'Admin',
+ qa_selector: 'admin_area_link',
**menu_data_tracking_attrs('admin')
},
id: 'admin',
title: 'Admin',
icon: 'admin',
- href: '/admin',
- css_class: 'qa-admin-area-link'
+ href: '/admin'
)
expect(subject[:secondary].first).to eq(expected_admin_item)
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 59624dc0682..497cd5d1e7f 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -76,7 +76,7 @@ RSpec.describe WikiHelper do
describe '#wiki_sort_controls' do
let(:wiki) { create(:project_wiki) }
let(:wiki_link) { helper.wiki_sort_controls(wiki, direction) }
- let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
+ let(:classes) { "gl-button btn btn-default btn-icon has-tooltip reverse-sort-btn rspec-reverse-sort" }
def expected_link(direction, icon_class)
path = "/#{wiki.project.full_path}/-/wikis/pages?direction=#{direction}"
diff --git a/spec/requests/web_ide/remote_ide_controller_spec.rb b/spec/requests/web_ide/remote_ide_controller_spec.rb
new file mode 100644
index 00000000000..9b99da3469c
--- /dev/null
+++ b/spec/requests/web_ide/remote_ide_controller_spec.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WebIde::RemoteIdeController, feature_category: :remote_development do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:user) { create(:user) }
+
+ let_it_be(:top_nav_partial) { 'layouts/header/_default' }
+
+ let_it_be(:connection_token) { 'random1Connection3Token7' }
+ let_it_be(:remote_path) { 'test/foo/README.md' }
+ let_it_be(:return_url) { 'https://example.com/-/original/location' }
+ let_it_be(:csp_nonce) { 'just=some=noncense' }
+
+ let(:remote_host) { 'my-remote-host.example.com:1234' }
+ let(:ff_vscode_web_ide) { true }
+
+ before do
+ sign_in(user)
+
+ stub_feature_flags(vscode_web_ide: ff_vscode_web_ide)
+
+ allow_next_instance_of(described_class) do |instance|
+ allow(instance).to receive(:content_security_policy_nonce).and_return(csp_nonce)
+ end
+ end
+
+ shared_examples_for '404 response' do
+ it 'has not_found status' do
+ post_to_remote_ide
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe "#index" do
+ context "when feature flag is on *and* user is not using legacy Web IDE" do
+ before do
+ post_to_remote_ide
+ end
+
+ it "renders the correct layout" do
+ expect(response).to render_template(layout: 'fullscreen')
+ end
+
+ it "renders with minimal: true" do
+ # This indirectly tests that `minimal: true` was passed to the fullscreen layout
+ expect(response).not_to render_template(top_nav_partial)
+ end
+
+ it "renders root element with data" do
+ expected = {
+ connection_token: connection_token,
+ remote_host: remote_host,
+ remote_path: remote_path,
+ return_url: return_url,
+ csp_nonce: csp_nonce
+ }
+
+ expect(find_root_element_data).to eq(expected)
+ end
+
+ it "updates the content security policy with the correct connect sources" do
+ expect(find_csp_connect_src).to include(
+ "ws://#{remote_host}",
+ "wss://#{remote_host}",
+ "http://#{remote_host}",
+ "https://#{remote_host}"
+ )
+ end
+ end
+
+ context 'when remote_host does not have port' do
+ let(:remote_host) { "my-remote-host.example.com" }
+
+ before do
+ post_to_remote_ide
+ end
+
+ it "updates the content security policy with the correct remote_host" do
+ expect(find_csp_connect_src).to include(
+ "ws://#{remote_host}",
+ "wss://#{remote_host}",
+ "http://#{remote_host}",
+ "https://#{remote_host}"
+ )
+ end
+
+ it 'renders remote_host in root element data' do
+ expect(find_root_element_data).to include(remote_host: remote_host)
+ end
+ end
+
+ context 'when feature flag is off' do
+ let(:ff_vscode_web_ide) { false }
+
+ it_behaves_like '404 response'
+ end
+
+ context "when the remote host is invalid" do
+ let(:remote_host) { 'invalid:host:1:1:' }
+
+ it_behaves_like '404 response'
+ end
+ end
+
+ def find_root_element_data
+ ide_attrs = Nokogiri::HTML.parse(response.body).at_css('#ide').attributes.transform_values(&:value)
+
+ {
+ connection_token: ide_attrs['data-connection-token'],
+ remote_host: ide_attrs['data-remote-host'],
+ remote_path: ide_attrs['data-remote-path'],
+ return_url: ide_attrs['data-return-url'],
+ csp_nonce: ide_attrs['data-csp-nonce']
+ }
+ end
+
+ def find_csp_connect_src
+ csp = response.headers['Content-Security-Policy']
+
+ # Transform "default-src foo bar; connect-src foo bar; script-src ..."
+ # into array of connect-src values
+ csp.split(';')
+ .map(&:strip)
+ .find { |entry| entry.starts_with?('connect-src') }
+ .split(' ')
+ .drop(1)
+ end
+
+ def post_to_remote_ide
+ params = {
+ connection_token: connection_token,
+ return_url: return_url
+ }
+
+ post ide_remote_path(remote_host: remote_host, remote_path: remote_path), params: params
+ end
+end
diff --git a/spec/routing/web_ide_routing_spec.rb b/spec/routing/web_ide_routing_spec.rb
new file mode 100644
index 00000000000..58c24189dfd
--- /dev/null
+++ b/spec/routing/web_ide_routing_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "Web IDE routing", feature_category: :remote_development do
+ describe 'remote' do
+ it "routes to #index, without remote_path" do
+ expect(post("/-/ide/remote/my.env.gitlab.example.com%3A3443")).to route_to(
+ "web_ide/remote_ide#index",
+ remote_host: 'my.env.gitlab.example.com:3443'
+ )
+ end
+
+ it "routes to #index, with remote_path" do
+ expect(post("/-/ide/remote/my.env.gitlab.example.com%3A3443/foo/bar.dev/test.dir")).to route_to(
+ "web_ide/remote_ide#index",
+ remote_host: 'my.env.gitlab.example.com:3443',
+ remote_path: 'foo/bar.dev/test.dir'
+ )
+ end
+ end
+end
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 33945509675..0368fd63357 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -4,6 +4,7 @@ RSpec.shared_examples "redis_shared_examples" do
include StubENV
let(:test_redis_url) { "redis://redishost:#{redis_port}" }
+ let(:test_cluster_config) { { cluster: [{ host: "redis://redishost", port: redis_port }] } }
let(:config_file_name) { instance_specific_config_file }
let(:config_old_format_socket) { "spec/fixtures/config/redis_old_format_socket.yml" }
let(:config_new_format_socket) { "spec/fixtures/config/redis_new_format_socket.yml" }
@@ -11,6 +12,7 @@ RSpec.shared_examples "redis_shared_examples" do
let(:new_socket_path) { "/path/to/redis.sock" }
let(:config_old_format_host) { "spec/fixtures/config/redis_old_format_host.yml" }
let(:config_new_format_host) { "spec/fixtures/config/redis_new_format_host.yml" }
+ let(:config_cluster_format_host) { "spec/fixtures/config/redis_cluster_format_host.yml" }
let(:redis_port) { 6379 }
let(:redis_database) { 99 }
let(:sentinel_port) { 26379 }
@@ -191,6 +193,30 @@ RSpec.shared_examples "redis_shared_examples" do
end
end
end
+
+ context 'with redis cluster format' do
+ let(:config_file_name) { config_cluster_format_host }
+
+ where(:rails_env, :host) do
+ [
+ %w[development development-master],
+ %w[test test-master],
+ %w[production production-master]
+ ]
+ end
+
+ with_them do
+ it 'returns hash with cluster and password' do
+ is_expected.to include(password: 'myclusterpassword',
+ cluster: [
+ { host: "#{host}1", port: redis_port },
+ { host: "#{host}2", port: redis_port }
+ ]
+ )
+ is_expected.not_to have_key(:url)
+ end
+ end
+ end
end
end
@@ -317,6 +343,14 @@ RSpec.shared_examples "redis_shared_examples" do
expect(subject).to eq(redis_database)
end
end
+
+ context 'with cluster-mode' do
+ let(:config_file_name) { config_cluster_format_host }
+
+ it 'returns the correct db' do
+ expect(subject).to eq(0)
+ end
+ end
end
describe '#sentinels' do
@@ -350,6 +384,14 @@ RSpec.shared_examples "redis_shared_examples" do
is_expected.to be_nil
end
end
+
+ context 'when cluster is defined' do
+ let(:config_file_name) { config_cluster_format_host }
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
end
describe '#sentinels?' do
@@ -370,6 +412,14 @@ RSpec.shared_examples "redis_shared_examples" do
is_expected.to be_falsey
end
end
+
+ context 'when cluster is defined' do
+ let(:config_file_name) { config_cluster_format_host }
+
+ it 'returns false' do
+ is_expected.to be_falsey
+ end
+ end
end
describe '#raw_config_hash' do
@@ -377,6 +427,11 @@ RSpec.shared_examples "redis_shared_examples" do
expect(subject).to receive(:fetch_config) { test_redis_url }
expect(subject.send(:raw_config_hash)).to eq(url: test_redis_url)
end
+
+ it 'returns cluster config without url key in a hash' do
+ expect(subject).to receive(:fetch_config) { test_cluster_config }
+ expect(subject.send(:raw_config_hash)).to eq(test_cluster_config)
+ end
end
describe '#fetch_config' do