# frozen_string_literal: true
describe StaticController do
fab!(:upload) { Fabricate(:upload) }
context '#favicon' do
let(:filename) { 'smallest.png' }
let(:file) { file_from_fixtures(filename) }
let(:upload) do
UploadCreator.new(file, filename).create_for(Discourse.system_user.id)
end
after do
Discourse.redis.scan_each(match: "memoize_*").each do |key|
Discourse.redis.del(key)
end
end
describe 'local store' do
it 'returns the default favicon if favicon has not been configured' do
get '/favicon/proxied'
expect(response.status).to eq(200)
expect(response.media_type).to eq('image/png')
expect(response.body.bytesize).to eq(SiteIconManager.favicon.filesize)
end
it 'returns the configured favicon' do
SiteSetting.favicon = upload
get '/favicon/proxied'
expect(response.status).to eq(200)
expect(response.media_type).to eq('image/png')
expect(response.body.bytesize).to eq(upload.filesize)
end
end
describe 'external store' do
let(:upload) do
Upload.create!(
url: '//s3-upload-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png',
original_filename: filename,
filesize: file.size,
user_id: Discourse.system_user.id
)
end
before do
setup_s3
end
it 'can proxy a favicon correctly' do
SiteSetting.favicon = upload
stub_request(:get, "https:/#{upload.url}")
.to_return(status: 200, body: file)
get '/favicon/proxied'
expect(response.status).to eq(200)
expect(response.media_type).to eq('image/png')
expect(response.body.bytesize).to eq(upload.filesize)
end
end
end
context '#brotli_asset' do
it 'returns a non brotli encoded 404 if asset is missing' do
get "/brotli_asset/missing.js"
expect(response.status).to eq(404)
expect(response.headers['Content-Encoding']).not_to eq('br')
expect(response.headers['Cache-Control']).to match(/max-age=1/)
end
it 'can handle fallback brotli assets' do
begin
assets_path = Rails.root.join("tmp/backup_assets")
GlobalSetting.stubs(:fallback_assets_path).returns(assets_path.to_s)
FileUtils.mkdir_p(assets_path)
file_path = assets_path.join("test.js.br")
File.write(file_path, 'fake brotli file')
get "/brotli_asset/test.js"
expect(response.status).to eq(200)
expect(response.headers["Cache-Control"]).to match(/public/)
ensure
File.delete(file_path)
end
end
it 'has correct headers for brotli assets' do
begin
assets_path = Rails.root.join("public/assets")
FileUtils.mkdir_p(assets_path)
file_path = assets_path.join("test.js.br")
File.write(file_path, 'fake brotli file')
get "/brotli_asset/test.js"
expect(response.status).to eq(200)
expect(response.headers["Cache-Control"]).to match(/public/)
ensure
File.delete(file_path)
end
end
it 'has correct cors headers for brotli assets' do
begin
assets_path = Rails.root.join("public/assets")
FileUtils.mkdir_p(assets_path)
file_path = assets_path.join("test.js.br")
File.write(file_path, 'fake brotli file')
GlobalSetting.stubs(:cdn_url).returns("https://www.example.com/")
get "/brotli_asset/test.js"
expect(response.status).to eq(200)
expect(response.headers["Access-Control-Allow-Origin"]).to match("*")
ensure
File.delete(file_path)
end
end
end
context '#cdn_asset' do
let (:site) { RailsMultisite::ConnectionManagement.current_db }
it 'can serve assets' do
begin
assets_path = Rails.root.join("public/assets")
FileUtils.mkdir_p(assets_path)
file_path = assets_path.join("test.js.br")
File.write(file_path, 'fake brotli file')
get "/cdn_asset/#{site}/test.js.br"
expect(response.status).to eq(200)
expect(response.headers["Cache-Control"]).to match(/public/)
ensure
File.delete(file_path)
end
end
end
context '#show' do
before do
post = create_post
SiteSetting.tos_topic_id = post.topic.id
SiteSetting.guidelines_topic_id = post.topic.id
SiteSetting.privacy_topic_id = post.topic.id
end
context "with a static file that's present" do
it "should return the right response for /faq" do
get "/faq"
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t('js.faq'))
expect(response.body).to include("
FAQ - Discourse")
end
end
[
['tos', :tos_url, I18n.t('js.tos')],
['privacy', :privacy_policy_url, I18n.t('js.privacy')]
].each do |id, setting_name, text|
context "#{id}" do
context "when #{setting_name} site setting is NOT set" do
it "renders the #{id} page" do
get "/#{id}"
expect(response.status).to eq(200)
expect(response.body).to include(text)
end
end
context "when #{setting_name} site setting is set" do
before do
SiteSetting.set(setting_name, 'http://example.com/page')
end
it "redirects to the #{setting_name}" do
get "/#{id}"
expect(response).to redirect_to('http://example.com/page')
end
end
end
end
context "with a missing file" do
it "should respond 404" do
get "/static/does-not-exist"
expect(response.status).to eq(404)
end
context "modal pages" do
it "should return the right response for /signup" do
get "/signup"
expect(response.status).to eq(200)
end
it "should return the right response for /password-reset" do
get "/password-reset"
expect(response.status).to eq(200)
end
end
end
it 'should redirect to / when logged in and path is /login' do
sign_in(Fabricate(:user))
get "/login"
expect(response).to redirect_to('/')
end
it "should display the login template when login is required" do
SiteSetting.login_required = true
get "/login"
expect(response.status).to eq(200)
expect(response.body).to include(PrettyText.cook(I18n.t(
'login_required.welcome_message', title: SiteSetting.title
)))
end
context "when login_required is enabled" do
before do
SiteSetting.login_required = true
end
['faq', 'guidelines', 'rules', 'conduct'].each do |page_name|
it "#{page_name} page redirects to login page for anon" do
get "/#{page_name}"
expect(response).to redirect_to '/login'
end
it "#{page_name} page redirects to login page for anon" do
get "/#{page_name}"
expect(response).to redirect_to '/login'
end
it "#{page_name} page loads for logged in user" do
sign_in(Fabricate(:user))
get "/#{page_name}"
expect(response.status).to eq(200)
expect(response.body).to include(I18n.t('js.guidelines'))
end
end
end
context "crawler view" do
it "should include correct title" do
get '/faq', headers: { 'HTTP_USER_AGENT' => 'Googlebot' }
expect(response.status).to eq(200)
expect(response.body).to include("FAQ - Discourse")
end
end
context "plugin api extensions" do
after do
Rails.application.reload_routes!
StaticController::CUSTOM_PAGES.clear
end
it "adds new topic-backed pages" do
routes = Proc.new do
get "contact" => "static#show", id: "contact"
end
Discourse::Application.routes.send(:eval_block, routes)
topic_id = Fabricate(:post, cooked: "contact info").topic_id
SiteSetting.setting(:test_contact_topic_id, topic_id)
Plugin::Instance.new.add_topic_static_page("contact", topic_id: "test_contact_topic_id")
get "/contact"
expect(response.status).to eq(200)
expect(response.body).to include("contact info")
end
it "replaces existing topic-backed pages" do
topic_id = Fabricate(:post, cooked: "Regular FAQ").topic_id
SiteSetting.setting(:test_faq_topic_id, topic_id)
polish_topic_id = Fabricate(:post, cooked: "Polish FAQ").topic_id
SiteSetting.setting(:test_polish_faq_topic_id, polish_topic_id)
Plugin::Instance.new.add_topic_static_page("faq") do
current_user&.locale == "pl" ? "test_polish_faq_topic_id" : "test_faq_topic_id"
end
get "/faq"
expect(response.status).to eq(200)
expect(response.body).to include("Regular FAQ")
sign_in(Fabricate(:user, locale: "pl"))
get "/faq"
expect(response.status).to eq(200)
expect(response.body).to include("Polish FAQ")
end
end
it "does not pollute SiteSetting.title (regression)" do
SiteSetting.title = "test"
SiteSetting.short_site_description = "something"
expect do
get "/login"
get "/login"
end.to_not change { SiteSetting.title }
end
end
describe '#enter' do
context 'without a redirect path' do
it 'redirects to the root url' do
post "/login.json"
expect(response).to redirect_to('/')
end
end
context 'with a redirect path' do
it 'redirects to the redirect path' do
post "/login.json", params: { redirect: '/foo' }
expect(response).to redirect_to('/foo')
end
end
context 'with a full url' do
it 'redirects to the correct path' do
post "/login.json", params: { redirect: "#{Discourse.base_url}/foo" }
expect(response).to redirect_to('/foo')
end
end
context 'with a redirect path with query params' do
it 'redirects to the redirect path and preserves query params' do
post "/login.json", params: { redirect: '/foo?bar=1' }
expect(response).to redirect_to('/foo?bar=1')
end
end
context 'with a period to force a new host' do
it 'redirects to the root path' do
post "/login.json", params: { redirect: ".org/foo" }
expect(response).to redirect_to('/')
end
end
context 'with a full url to someone else' do
it 'redirects to the root path' do
post "/login.json", params: { redirect: "http://eviltrout.com/foo" }
expect(response).to redirect_to('/')
end
end
context 'with an invalid URL' do
it "redirects to the root" do
post "/login.json", params: { redirect: "javascript:alert('trout')" }
expect(response).to redirect_to('/')
end
end
context 'with an array' do
it "redirects to the root" do
post "/login.json", params: { redirect: ["/foo"] }
expect(response.status).to eq(400)
json = response.parsed_body
expect(json["errors"]).to be_present
expect(json["errors"]).to include(
I18n.t("invalid_params", message: "redirect")
)
end
end
context 'when the redirect path is the login page' do
it 'redirects to the root url' do
post "/login.json", params: { redirect: login_path }
expect(response).to redirect_to('/')
end
end
end
describe "#service_worker_asset" do
it "works" do
get "/service-worker.js"
expect(response.status).to eq(200)
expect(response.content_type).to start_with("application/javascript")
expect(response.body).to include("workbox")
end
it "replaces sourcemap URL" do
Rails.application.assets_manifest.stubs(:find_sources).with("service-worker.js").returns([
<<~JS
someFakeServiceWorkerSource();
//# sourceMappingURL=service-worker-abcde.js.map
JS
])
{
'/assets/service-worker.js' => '/assets/service-worker-abcde.js.map',
'/assets/service-worker.js.br' => '/assets/service-worker-abcde.js.map',
'/assets/service-worker.br.js' => '/assets/service-worker-abcde.js.map',
'/assets/service-worker.js.gz' => '/assets/service-worker-abcde.js.map',
'/assets/service-worker.gz.js' => '/assets/service-worker-abcde.js.map',
'https://example.com/assets/service-worker.js' => 'https://example.com/assets/service-worker-abcde.js.map',
'https://example.com/subfolder/assets/service-worker.js' => 'https://example.com/subfolder/assets/service-worker-abcde.js.map',
}.each do |asset_path, expected_map_url|
ActionController::Base.helpers.stubs(:asset_path).with("service-worker.js").returns(asset_path)
get "/service-worker.js"
expect(response.status).to eq(200)
expect(response.content_type).to start_with("application/javascript")
expect(response.body).to include("sourceMappingURL=#{expected_map_url}\n")
end
end
end
end