debian-mirror-gitlab/spec/requests/web_ide/remote_ide_controller_spec.rb
2023-03-04 22:38:38 +05:30

145 lines
4.1 KiB
Ruby

# 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_source('connect-src')).to include(
"ws://#{remote_host}",
"wss://#{remote_host}",
"http://#{remote_host}",
"https://#{remote_host}"
)
end
it "updates the content security policy with the correct frame sources" do
expect(find_csp_source('frame-src')).to include("https://*.vscode-cdn.net/")
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_source('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_source(key)
csp = response.headers['Content-Security-Policy']
# Transform "default-src foo bar; connect-src foo bar; script-src ..."
# into array of values for a single directive based on the given key
csp.split(';')
.map(&:strip)
.find { |entry| entry.starts_with?(key) }
.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