2019-04-30 10:27:42 +10:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2018-03-27 18:04:40 +05:30
|
|
|
RSpec.describe ApplicationController do
|
|
|
|
|
describe "#redirect_to_login_if_required" do
|
|
|
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
admin # to skip welcome wizard at home page `/`
|
|
|
|
|
SiteSetting.login_required = true
|
|
|
|
|
end
|
|
|
|
|
|
2018-11-09 11:14:35 +11:00
|
|
|
it "should never cache a login redirect" do
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.headers["Cache-Control"]).to eq("no-cache, no-store")
|
|
|
|
|
end
|
2019-11-13 17:28:12 +00:00
|
|
|
|
|
|
|
|
it "should redirect to login normally" do
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should redirect to SSO if enabled" do
|
2021-02-08 10:04:33 +00:00
|
|
|
SiteSetting.discourse_connect_url = "http://someurl.com"
|
|
|
|
|
SiteSetting.enable_discourse_connect = true
|
2019-11-13 17:28:12 +00:00
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/session/sso")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should redirect to authenticator if only one, and local logins disabled" do
|
|
|
|
|
# Local logins and google enabled, direct to login UI
|
|
|
|
|
SiteSetting.enable_google_oauth2_logins = true
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
|
|
|
|
|
# Only google enabled, login immediately
|
|
|
|
|
SiteSetting.enable_local_logins = false
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/auth/google_oauth2")
|
|
|
|
|
|
|
|
|
|
# Google and GitHub enabled, direct to login UI
|
|
|
|
|
SiteSetting.enable_github_logins = true
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
end
|
2019-11-19 19:15:11 +00:00
|
|
|
|
2021-02-08 10:04:33 +00:00
|
|
|
it "should not redirect to SSO when auth_immediately is disabled" do
|
|
|
|
|
SiteSetting.auth_immediately = false
|
|
|
|
|
SiteSetting.discourse_connect_url = "http://someurl.com"
|
|
|
|
|
SiteSetting.enable_discourse_connect = true
|
2020-09-24 17:06:07 +01:00
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
end
|
|
|
|
|
|
2021-02-08 10:04:33 +00:00
|
|
|
it "should not redirect to authenticator when auth_immediately is disabled" do
|
|
|
|
|
SiteSetting.auth_immediately = false
|
2020-09-24 17:06:07 +01:00
|
|
|
SiteSetting.enable_google_oauth2_logins = true
|
|
|
|
|
SiteSetting.enable_local_logins = false
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
end
|
|
|
|
|
|
2019-11-19 19:15:11 +00:00
|
|
|
context "with omniauth in test mode" do
|
|
|
|
|
before do
|
|
|
|
|
OmniAuth.config.test_mode = true
|
|
|
|
|
OmniAuth.config.add_mock(
|
|
|
|
|
:google_oauth2,
|
|
|
|
|
info: OmniAuth::AuthHash::InfoHash.new(email: "address@example.com"),
|
|
|
|
|
extra: {
|
|
|
|
|
raw_info: OmniAuth::AuthHash.new(email_verified: true, email: "address@example.com"),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after do
|
|
|
|
|
Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[
|
|
|
|
|
:google_oauth2
|
|
|
|
|
] = nil
|
|
|
|
|
OmniAuth.config.test_mode = false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not redirect to authenticator if registration in progress" do
|
|
|
|
|
SiteSetting.enable_local_logins = false
|
|
|
|
|
SiteSetting.enable_google_oauth2_logins = true
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/auth/google_oauth2")
|
|
|
|
|
|
|
|
|
|
expect(cookies[:authentication_data]).to eq(nil)
|
|
|
|
|
|
|
|
|
|
get "/auth/google_oauth2/callback.json"
|
|
|
|
|
expect(response).to redirect_to("/")
|
|
|
|
|
expect(cookies[:authentication_data]).not_to eq(nil)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/login")
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-03-21 13:24:49 -07:00
|
|
|
|
|
|
|
|
it "contains authentication data when cookies exist" do
|
2021-01-25 13:47:44 +00:00
|
|
|
cookie_data = "someauthenticationdata"
|
|
|
|
|
cookies["authentication_data"] = cookie_data
|
2020-03-21 13:24:49 -07:00
|
|
|
get "/login"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-01-25 13:47:44 +00:00
|
|
|
expect(response.body).to include("data-authentication-data=\"#{cookie_data}\"")
|
|
|
|
|
expect(response.headers["Set-Cookie"]).to include("authentication_data=;") # Delete cookie
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "deletes authentication data cookie even if already authenticated" do
|
|
|
|
|
sign_in(Fabricate(:user))
|
|
|
|
|
cookies["authentication_data"] = "someauthenticationdata"
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.body).not_to include("data-authentication-data=")
|
|
|
|
|
expect(response.headers["Set-Cookie"]).to include("authentication_data=;") # Delete cookie
|
2020-03-21 13:24:49 -07:00
|
|
|
end
|
2021-12-02 15:12:25 +00:00
|
|
|
|
|
|
|
|
it "returns a 403 for json requests" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
|
|
|
|
|
get "/latest.json"
|
|
|
|
|
expect(response.status).to eq(403)
|
|
|
|
|
end
|
2018-03-27 18:04:40 +05:30
|
|
|
end
|
2018-05-23 16:58:47 -04:00
|
|
|
|
2019-03-15 13:09:37 +02:00
|
|
|
describe "#redirect_to_second_factor_if_required" do
|
|
|
|
|
let(:admin) { Fabricate(:admin) }
|
2024-01-25 14:28:26 +08:00
|
|
|
fab!(:user) { Fabricate(:user, refresh_auto_groups: true) }
|
2019-03-15 13:09:37 +02:00
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
admin # to skip welcome wizard at home page `/`
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should redirect admins when enforce_second_factor is 'all'" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "all"
|
|
|
|
|
sign_in(admin)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should redirect users when enforce_second_factor is 'all'" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "all"
|
|
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/u/#{user.username}/preferences/second-factor")
|
|
|
|
|
end
|
|
|
|
|
|
2019-06-28 00:01:27 +02:00
|
|
|
it "should not redirect anonymous users when enforce_second_factor is 'all'" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "all"
|
|
|
|
|
SiteSetting.allow_anonymous_posting = true
|
2024-01-25 14:28:26 +08:00
|
|
|
|
2019-06-28 00:01:27 +02:00
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
|
|
post "/u/toggle-anon.json"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
|
2019-03-15 13:09:37 +02:00
|
|
|
it "should redirect admins when enforce_second_factor is 'staff'" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "staff"
|
|
|
|
|
sign_in(admin)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not redirect users when enforce_second_factor is 'staff'" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "staff"
|
|
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not redirect admins when turned off" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "no"
|
|
|
|
|
sign_in(admin)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not redirect users when turned off" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "no"
|
|
|
|
|
sign_in(user)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
2020-01-15 11:27:12 +01:00
|
|
|
|
2020-06-05 18:31:58 +02:00
|
|
|
it "correctly redirects for Unicode usernames" do
|
|
|
|
|
SiteSetting.enforce_second_factor = "all"
|
|
|
|
|
SiteSetting.unicode_usernames = true
|
|
|
|
|
user = sign_in(Fabricate(:unicode_user))
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/u/#{user.encoded_username}/preferences/second-factor")
|
|
|
|
|
end
|
|
|
|
|
|
2020-01-15 11:27:12 +01:00
|
|
|
context "when enforcing second factor for staff" do
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.enforce_second_factor = "staff"
|
|
|
|
|
sign_in(admin)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the staff member has not enabled TOTP or security keys" do
|
|
|
|
|
it "redirects the staff to the second factor preferences" do
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the staff member has enabled TOTP" do
|
|
|
|
|
before { Fabricate(:user_second_factor_totp, user: admin) }
|
|
|
|
|
|
|
|
|
|
it "does not redirects the staff to set up 2FA" do
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the staff member has enabled security keys" do
|
|
|
|
|
before { Fabricate(:user_security_key_with_random_credential, user: admin) }
|
|
|
|
|
|
|
|
|
|
it "does not redirects the staff to set up 2FA" do
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-03-15 13:09:37 +02:00
|
|
|
end
|
|
|
|
|
|
2018-10-05 14:33:08 +10:00
|
|
|
describe "invalid request params" do
|
|
|
|
|
before do
|
|
|
|
|
@old_logger = Rails.logger
|
|
|
|
|
@logs = StringIO.new
|
|
|
|
|
Rails.logger = Logger.new(@logs)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after { Rails.logger = @old_logger }
|
|
|
|
|
|
|
|
|
|
it "should not raise a 500 (nor should it log a warning) for bad params" do
|
2019-04-30 10:27:42 +10:00
|
|
|
bad_str = (+"d\xDE").force_encoding("utf-8")
|
2018-10-05 14:33:08 +10:00
|
|
|
expect(bad_str.valid_encoding?).to eq(false)
|
|
|
|
|
|
|
|
|
|
get "/latest.json", params: { test: bad_str }
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(400)
|
2018-12-03 11:31:12 +11:00
|
|
|
|
|
|
|
|
log = @logs.string
|
|
|
|
|
|
|
|
|
|
if (log.include? "exception app middleware")
|
|
|
|
|
# heisentest diagnostics
|
|
|
|
|
puts
|
2021-05-20 21:43:47 -04:00
|
|
|
puts "EXTRA DIAGNOSTICS FOR INTERMITTENT TEST FAIL"
|
2018-12-03 11:31:12 +11:00
|
|
|
puts log
|
|
|
|
|
puts ">> action_dispatch.exception"
|
|
|
|
|
ex = request.env["action_dispatch.exception"]
|
|
|
|
|
puts ">> exception class: #{ex.class} : #{ex}"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
expect(log).not_to include("exception app middleware")
|
|
|
|
|
|
2021-04-21 12:36:32 +03:00
|
|
|
expect(response.parsed_body).to eq("status" => 400, "error" => "Bad Request")
|
2018-10-05 14:33:08 +10:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-10-30 20:02:48 -06:00
|
|
|
describe "missing required param" do
|
|
|
|
|
it "should return a 400" do
|
|
|
|
|
get "/search/query.json", params: { trem: "misspelled term" }
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(400)
|
2021-04-21 12:36:32 +03:00
|
|
|
expect(response.parsed_body["errors"].first).to include(
|
|
|
|
|
"param is missing or the value is empty: term",
|
|
|
|
|
)
|
2018-10-30 20:02:48 -06:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-05-23 16:58:47 -04:00
|
|
|
describe "build_not_found_page" do
|
|
|
|
|
describe "topic not found" do
|
2018-08-20 13:10:49 +10:00
|
|
|
it "should not redirect to permalink if topic/category does not exist" do
|
|
|
|
|
topic = create_post.topic
|
|
|
|
|
Permalink.create!(url: topic.relative_url, topic_id: topic.id + 1)
|
|
|
|
|
topic.trash!
|
2019-10-08 14:15:08 +03:00
|
|
|
|
|
|
|
|
SiteSetting.detailed_404 = false
|
|
|
|
|
get topic.relative_url
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
|
|
|
|
|
SiteSetting.detailed_404 = true
|
2018-08-20 13:10:49 +10:00
|
|
|
get topic.relative_url
|
|
|
|
|
expect(response.status).to eq(410)
|
|
|
|
|
end
|
|
|
|
|
|
2018-08-09 15:05:12 +10:00
|
|
|
it "should return permalink for deleted topics" do
|
|
|
|
|
topic = create_post.topic
|
|
|
|
|
external_url = "https://somewhere.over.rainbow"
|
|
|
|
|
Permalink.create!(url: topic.relative_url, external_url: external_url)
|
|
|
|
|
topic.trash!
|
|
|
|
|
|
|
|
|
|
get topic.relative_url
|
|
|
|
|
expect(response.status).to eq(301)
|
|
|
|
|
expect(response).to redirect_to(external_url)
|
|
|
|
|
|
|
|
|
|
get "/t/#{topic.id}.json"
|
|
|
|
|
expect(response.status).to eq(301)
|
|
|
|
|
expect(response).to redirect_to(external_url)
|
|
|
|
|
|
|
|
|
|
get "/t/#{topic.id}.json", xhr: true
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.body).to eq(external_url)
|
|
|
|
|
end
|
|
|
|
|
|
2018-08-09 11:05:08 -04:00
|
|
|
it "supports subfolder with permalinks" do
|
2019-11-15 16:48:24 +11:00
|
|
|
set_subfolder "/forum"
|
2018-08-09 11:05:08 -04:00
|
|
|
|
|
|
|
|
trashed_topic = create_post.topic
|
|
|
|
|
trashed_topic.trash!
|
|
|
|
|
new_topic = create_post.topic
|
|
|
|
|
permalink = Permalink.create!(url: trashed_topic.relative_url, topic_id: new_topic.id)
|
|
|
|
|
|
|
|
|
|
# no subfolder because router doesn't know about subfolder in this test
|
|
|
|
|
get "/t/#{trashed_topic.slug}/#{trashed_topic.id}"
|
|
|
|
|
expect(response.status).to eq(301)
|
|
|
|
|
expect(response).to redirect_to("/forum/t/#{new_topic.slug}/#{new_topic.id}")
|
|
|
|
|
|
|
|
|
|
permalink.destroy
|
|
|
|
|
category = Fabricate(:category)
|
|
|
|
|
permalink = Permalink.create!(url: trashed_topic.relative_url, category_id: category.id)
|
|
|
|
|
get "/t/#{trashed_topic.slug}/#{trashed_topic.id}"
|
|
|
|
|
expect(response.status).to eq(301)
|
2020-03-20 22:26:45 +00:00
|
|
|
expect(response).to redirect_to("/forum/c/#{category.slug}/#{category.id}")
|
2018-08-09 11:05:08 -04:00
|
|
|
|
|
|
|
|
permalink.destroy
|
|
|
|
|
permalink =
|
|
|
|
|
Permalink.create!(url: trashed_topic.relative_url, post_id: new_topic.posts.last.id)
|
|
|
|
|
get "/t/#{trashed_topic.slug}/#{trashed_topic.id}"
|
|
|
|
|
expect(response.status).to eq(301)
|
|
|
|
|
expect(response).to redirect_to(
|
|
|
|
|
"/forum/t/#{new_topic.slug}/#{new_topic.id}/#{new_topic.posts.last.post_number}",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
2019-05-17 10:07:18 +08:00
|
|
|
it "should return 404 and show Google search for an invalid topic route" do
|
2018-05-23 16:58:47 -04:00
|
|
|
get "/t/nope-nope/99999999"
|
2019-05-17 10:07:18 +08:00
|
|
|
|
2018-05-23 16:58:47 -04:00
|
|
|
expect(response.status).to eq(404)
|
2019-05-17 10:07:18 +08:00
|
|
|
|
|
|
|
|
response_body = response.body
|
|
|
|
|
|
|
|
|
|
expect(response_body).to include(I18n.t("page_not_found.search_button"))
|
2019-10-08 14:15:08 +03:00
|
|
|
expect(response_body).to have_tag("input", with: { value: "nope nope" })
|
2018-05-23 16:58:47 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not include Google search if login_required is enabled" do
|
|
|
|
|
SiteSetting.login_required = true
|
|
|
|
|
sign_in(Fabricate(:user))
|
|
|
|
|
get "/t/nope-nope/99999999"
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to_not include("google.com/search")
|
|
|
|
|
end
|
2019-02-12 12:20:33 +02:00
|
|
|
|
2019-02-14 17:58:16 +11:00
|
|
|
describe "no logspam" do
|
|
|
|
|
before do
|
|
|
|
|
@orig_logger = Rails.logger
|
|
|
|
|
Rails.logger = @fake_logger = FakeLogger.new
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after { Rails.logger = @orig_logger }
|
|
|
|
|
|
|
|
|
|
it "should handle 404 to a css file" do
|
2020-12-07 12:24:18 +00:00
|
|
|
Discourse.cache.delete("page_not_found_topics:#{I18n.locale}")
|
2019-02-14 17:58:16 +11:00
|
|
|
|
|
|
|
|
topic1 = Fabricate(:topic)
|
|
|
|
|
get "/stylesheets/mobile_1_4cd559272273fe6d3c7db620c617d596a5fdf240.css",
|
|
|
|
|
headers: {
|
|
|
|
|
"HTTP_ACCEPT" => "text/css,*/*,q=0.1",
|
|
|
|
|
}
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to include(topic1.title)
|
|
|
|
|
|
|
|
|
|
topic2 = Fabricate(:topic)
|
|
|
|
|
get "/stylesheets/mobile_1_4cd559272273fe6d3c7db620c617d596a5fdf240.css",
|
|
|
|
|
headers: {
|
|
|
|
|
"HTTP_ACCEPT" => "text/css,*/*,q=0.1",
|
|
|
|
|
}
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to include(topic1.title)
|
|
|
|
|
expect(response.body).to_not include(topic2.title)
|
|
|
|
|
|
2022-05-05 03:53:54 +02:00
|
|
|
expect(@fake_logger.fatals.length).to eq(0)
|
|
|
|
|
expect(@fake_logger.errors.length).to eq(0)
|
|
|
|
|
expect(@fake_logger.warnings.length).to eq(0)
|
2019-02-14 17:58:16 +11:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-02-12 12:20:33 +02:00
|
|
|
it "should cache results" do
|
2020-12-07 12:24:18 +00:00
|
|
|
Discourse.cache.delete("page_not_found_topics:#{I18n.locale}")
|
|
|
|
|
Discourse.cache.delete("page_not_found_topics:fr")
|
2019-02-12 12:20:33 +02:00
|
|
|
|
|
|
|
|
topic1 = Fabricate(:topic)
|
|
|
|
|
get "/t/nope-nope/99999999"
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to include(topic1.title)
|
|
|
|
|
|
|
|
|
|
topic2 = Fabricate(:topic)
|
|
|
|
|
get "/t/nope-nope/99999999"
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to include(topic1.title)
|
|
|
|
|
expect(response.body).to_not include(topic2.title)
|
2020-12-07 12:24:18 +00:00
|
|
|
|
|
|
|
|
# Different locale should have different cache
|
|
|
|
|
SiteSetting.default_locale = :fr
|
|
|
|
|
get "/t/nope-nope/99999999"
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expect(response.body).to include(topic1.title)
|
|
|
|
|
expect(response.body).to include(topic2.title)
|
2019-02-12 12:20:33 +02:00
|
|
|
end
|
2018-05-23 16:58:47 -04:00
|
|
|
end
|
|
|
|
|
end
|
2018-07-04 10:07:14 +10:00
|
|
|
|
2018-08-08 07:46:34 +03:00
|
|
|
describe "#handle_theme" do
|
2019-05-07 03:12:20 +00:00
|
|
|
let!(:theme) { Fabricate(:theme, user_selectable: true) }
|
|
|
|
|
let!(:theme2) { Fabricate(:theme, user_selectable: true) }
|
|
|
|
|
let!(:non_selectable_theme) { Fabricate(:theme, user_selectable: false) }
|
2023-11-09 16:47:59 -06:00
|
|
|
fab!(:user)
|
|
|
|
|
fab!(:admin)
|
2018-08-08 07:46:34 +03:00
|
|
|
|
|
|
|
|
before { sign_in(user) }
|
|
|
|
|
|
|
|
|
|
it "selects the theme the user has selected" do
|
|
|
|
|
user.user_option.update_columns(theme_ids: [theme.id])
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(theme.id)
|
2018-08-08 07:46:34 +03:00
|
|
|
|
|
|
|
|
theme.update_attribute(:user_selectable, false)
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(SiteSetting.default_theme_id)
|
2018-08-08 07:46:34 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "can be overridden with a cookie" do
|
|
|
|
|
user.user_option.update_columns(theme_ids: [theme.id])
|
|
|
|
|
|
|
|
|
|
cookies["theme_ids"] = "#{theme2.id}|#{user.user_option.theme_key_seq}"
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(theme2.id)
|
2018-08-08 07:46:34 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "falls back to the default theme when the user has no cookies or preferences" do
|
|
|
|
|
user.user_option.update_columns(theme_ids: [])
|
|
|
|
|
cookies["theme_ids"] = nil
|
|
|
|
|
theme2.set_default!
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(theme2.id)
|
2018-08-08 07:46:34 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "can be overridden with preview_theme_id param" do
|
|
|
|
|
sign_in(admin)
|
2021-06-15 14:57:17 +08:00
|
|
|
cookies["theme_ids"] = "#{theme.id}|#{admin.user_option.theme_key_seq}"
|
2018-08-08 07:46:34 +03:00
|
|
|
|
|
|
|
|
get "/", params: { preview_theme_id: theme2.id }
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(theme2.id)
|
2018-09-07 10:44:57 +10:00
|
|
|
|
|
|
|
|
get "/", params: { preview_theme_id: non_selectable_theme.id }
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(non_selectable_theme.id)
|
2018-09-07 10:44:57 +10:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not allow non privileged user to preview themes" do
|
|
|
|
|
sign_in(user)
|
|
|
|
|
get "/", params: { preview_theme_id: non_selectable_theme.id }
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(SiteSetting.default_theme_id)
|
2018-08-08 07:46:34 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "cookie can fail back to user if out of sync" do
|
|
|
|
|
user.user_option.update_columns(theme_ids: [theme.id])
|
|
|
|
|
cookies["theme_ids"] = "#{theme2.id}|#{user.user_option.theme_key_seq - 1}"
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.status).to eq(200)
|
2021-06-15 14:57:17 +08:00
|
|
|
expect(controller.theme_id).to eq(theme.id)
|
2018-08-08 07:46:34 +03:00
|
|
|
end
|
|
|
|
|
end
|
2018-10-22 13:22:23 -04:00
|
|
|
|
2018-11-15 15:22:02 +11:00
|
|
|
describe "Custom hostname" do
|
|
|
|
|
it "does not allow arbitrary host injection" do
|
|
|
|
|
get("/latest", headers: { "X-Forwarded-Host" => "test123.com" })
|
|
|
|
|
|
|
|
|
|
expect(response.body).not_to include("test123")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-12-06 03:15:09 +05:30
|
|
|
describe "allow_embedding_site_in_an_iframe" do
|
|
|
|
|
it "should have the 'X-Frame-Options' header with value 'sameorigin'" do
|
|
|
|
|
get("/latest")
|
|
|
|
|
expect(response.headers["X-Frame-Options"]).to eq("SAMEORIGIN")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "should not include the 'X-Frame-Options' header" do
|
|
|
|
|
SiteSetting.allow_embedding_site_in_an_iframe = true
|
|
|
|
|
get("/latest")
|
|
|
|
|
expect(response.headers).not_to include("X-Frame-Options")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-08-31 20:50:06 +08:00
|
|
|
describe "setting `Cross-Origin-Opener-Policy` header" do
|
|
|
|
|
describe "when `cross_origin_opener_policy_header` site setting is set to `same-origin`" do
|
|
|
|
|
before { SiteSetting.cross_origin_opener_policy_header = "same-origin" }
|
|
|
|
|
|
|
|
|
|
it "sets `Cross-Origin-Opener-Policy` header to `same-origin`" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.headers["Cross-Origin-Opener-Policy"]).to eq("same-origin")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not set the `Cross-Origin-Opener-Policy` header for a JSON request" do
|
|
|
|
|
get "/latest.json"
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.headers["Cross-Origin-Opener-Policy"]).to eq(nil)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "when `cross_origin_opener_policy_header` site setting is set to `unsafe-none`" do
|
|
|
|
|
it "does not set the `Cross-Origin-Opener-Policy` header" do
|
|
|
|
|
SiteSetting.cross_origin_opener_policy_header = "unsafe-none"
|
|
|
|
|
|
|
|
|
|
get "/latest"
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
2023-12-19 11:46:44 +08:00
|
|
|
expect(response.headers["Cross-Origin-Opener-Policy"]).to eq("unsafe-none")
|
2023-08-31 20:50:06 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-06-22 04:35:46 +08:00
|
|
|
describe "splash_screen" do
|
|
|
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
|
|
|
|
|
|
before { admin }
|
|
|
|
|
|
|
|
|
|
it "adds a preloader splash screen when enabled" do
|
|
|
|
|
get "/"
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
2022-07-05 02:06:28 +08:00
|
|
|
expect(response.body).to include("d-splash")
|
2022-06-22 04:35:46 +08:00
|
|
|
|
2022-07-05 02:06:28 +08:00
|
|
|
SiteSetting.splash_screen = false
|
2022-06-22 04:35:46 +08:00
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(200)
|
2022-07-05 02:06:28 +08:00
|
|
|
expect(response.body).not_to include("d-splash")
|
2022-06-22 04:35:46 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-04-01 13:18:53 -04:00
|
|
|
describe "Delegated auth" do
|
|
|
|
|
let :public_key do
|
|
|
|
|
<<~TXT
|
|
|
|
|
-----BEGIN PUBLIC KEY-----
|
|
|
|
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDh7BS7Ey8hfbNhlNAW/47pqT7w
|
|
|
|
|
IhBz3UyBYzin8JurEQ2pY9jWWlY8CH147KyIZf1fpcsi7ZNxGHeDhVsbtUKZxnFV
|
|
|
|
|
p16Op3CHLJnnJKKBMNdXMy0yDfCAHZtqxeBOTcCo1Vt/bHpIgiK5kmaekyXIaD0n
|
|
|
|
|
w0z/BYpOgZ8QwnI5ZwIDAQAB
|
|
|
|
|
-----END PUBLIC KEY-----
|
|
|
|
|
TXT
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
let :args do
|
|
|
|
|
{ auth_redirect: "http://no-good.com", user_api_public_key: "not-a-valid-public-key" }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "disallows invalid public_key param" do
|
|
|
|
|
args[:auth_redirect] = "discourse://auth_redirect"
|
|
|
|
|
get "/latest", params: args
|
|
|
|
|
|
|
|
|
|
expect(response.body).to eq(I18n.t("user_api_key.invalid_public_key"))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not allow invalid auth_redirect" do
|
|
|
|
|
args[:user_api_public_key] = public_key
|
|
|
|
|
get "/latest", params: args
|
|
|
|
|
|
|
|
|
|
expect(response.body).to eq(I18n.t("user_api_key.invalid_auth_redirect"))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not redirect if one_time_password scope is disallowed" do
|
|
|
|
|
SiteSetting.allow_user_api_key_scopes = "read|write"
|
|
|
|
|
args[:user_api_public_key] = public_key
|
|
|
|
|
args[:auth_redirect] = "discourse://auth_redirect"
|
|
|
|
|
|
|
|
|
|
get "/latest", params: args
|
|
|
|
|
|
|
|
|
|
expect(response.status).to_not eq(302)
|
|
|
|
|
expect(response).to_not redirect_to("#{args[:auth_redirect]}?otp=true")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "redirects correctly with valid params" do
|
2019-04-01 22:13:53 -04:00
|
|
|
SiteSetting.login_required = true
|
2019-04-01 13:18:53 -04:00
|
|
|
args[:user_api_public_key] = public_key
|
|
|
|
|
args[:auth_redirect] = "discourse://auth_redirect"
|
|
|
|
|
|
|
|
|
|
get "/categories", params: args
|
|
|
|
|
|
|
|
|
|
expect(response.status).to eq(302)
|
|
|
|
|
expect(response).to redirect_to("#{args[:auth_redirect]}?otp=true")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2018-10-22 13:22:23 -04:00
|
|
|
describe "Content Security Policy" do
|
|
|
|
|
it "is enabled by SiteSettings" do
|
|
|
|
|
SiteSetting.content_security_policy = false
|
|
|
|
|
SiteSetting.content_security_policy_report_only = false
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
|
|
|
|
|
expect(response.headers).to_not include("Content-Security-Policy")
|
|
|
|
|
expect(response.headers).to_not include("Content-Security-Policy-Report-Only")
|
|
|
|
|
|
|
|
|
|
SiteSetting.content_security_policy = true
|
|
|
|
|
SiteSetting.content_security_policy_report_only = true
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
|
|
|
|
|
expect(response.headers).to include("Content-Security-Policy")
|
|
|
|
|
expect(response.headers).to include("Content-Security-Policy-Report-Only")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "can be customized with SiteSetting" do
|
|
|
|
|
SiteSetting.content_security_policy = true
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
|
|
|
|
|
|
|
|
|
expect(script_src).to_not include("example.com")
|
|
|
|
|
|
|
|
|
|
SiteSetting.content_security_policy_script_src = "example.com"
|
|
|
|
|
|
|
|
|
|
get "/"
|
|
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
|
|
|
|
|
|
|
|
|
expect(script_src).to include("example.com")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not set CSP when responding to non-HTML" do
|
|
|
|
|
SiteSetting.content_security_policy = true
|
|
|
|
|
SiteSetting.content_security_policy_report_only = true
|
|
|
|
|
|
|
|
|
|
get "/latest.json"
|
|
|
|
|
|
|
|
|
|
expect(response.headers).to_not include("Content-Security-Policy")
|
|
|
|
|
expect(response.headers).to_not include("Content-Security-Policy-Report-Only")
|
|
|
|
|
end
|
|
|
|
|
|
2021-03-26 11:19:31 -04:00
|
|
|
it "when GTM is enabled it adds the same nonce to the policy and the GTM tag" do
|
|
|
|
|
SiteSetting.content_security_policy = true
|
2023-07-28 12:53:44 +01:00
|
|
|
SiteSetting.content_security_policy_report_only = true
|
2021-03-26 11:19:31 -04:00
|
|
|
SiteSetting.gtm_container_id = "GTM-ABCDEF"
|
|
|
|
|
|
|
|
|
|
get "/latest"
|
2023-07-11 15:24:36 -06:00
|
|
|
|
|
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
2023-07-28 12:53:44 +01:00
|
|
|
report_only_script_src =
|
|
|
|
|
parse(response.headers["Content-Security-Policy-Report-Only"])["script-src"]
|
|
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
nonce = extract_nonce_from_script_src(script_src)
|
2023-07-28 12:53:44 +01:00
|
|
|
report_only_nonce = extract_nonce_from_script_src(report_only_script_src)
|
|
|
|
|
|
|
|
|
|
expect(nonce).to eq(report_only_nonce)
|
2023-07-11 15:24:36 -06:00
|
|
|
|
|
|
|
|
gtm_meta_tag = Nokogiri::HTML5.fragment(response.body).css("#data-google-tag-manager").first
|
|
|
|
|
expect(gtm_meta_tag["data-nonce"]).to eq(nonce)
|
|
|
|
|
end
|
|
|
|
|
|
2023-07-28 12:53:44 +01:00
|
|
|
it "doesn't reuse nonces between requests" do
|
|
|
|
|
global_setting :anon_cache_store_threshold, 1
|
|
|
|
|
Middleware::AnonymousCache.enable_anon_cache
|
|
|
|
|
Middleware::AnonymousCache.clear_all_cache!
|
|
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
SiteSetting.content_security_policy = true
|
2023-07-28 12:53:44 +01:00
|
|
|
SiteSetting.content_security_policy_report_only = true
|
2023-07-11 15:24:36 -06:00
|
|
|
SiteSetting.gtm_container_id = "GTM-ABCDEF"
|
2021-03-26 11:19:31 -04:00
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
get "/latest"
|
|
|
|
|
|
2023-07-28 12:53:44 +01:00
|
|
|
expect(response.headers["X-Discourse-Cached"]).to eq("store")
|
|
|
|
|
expect(response.headers).not_to include("Discourse-GTM-Nonce-Placeholder")
|
|
|
|
|
|
2021-03-26 11:19:31 -04:00
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
2023-07-28 12:53:44 +01:00
|
|
|
report_only_script_src =
|
|
|
|
|
parse(response.headers["Content-Security-Policy-Report-Only"])["script-src"]
|
|
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
first_nonce = extract_nonce_from_script_src(script_src)
|
2023-07-28 12:53:44 +01:00
|
|
|
first_report_only_nonce = extract_nonce_from_script_src(report_only_script_src)
|
|
|
|
|
|
|
|
|
|
expect(first_nonce).to eq(first_report_only_nonce)
|
2023-07-11 15:24:36 -06:00
|
|
|
|
|
|
|
|
gtm_meta_tag = Nokogiri::HTML5.fragment(response.body).css("#data-google-tag-manager").first
|
|
|
|
|
expect(gtm_meta_tag["data-nonce"]).to eq(first_nonce)
|
|
|
|
|
|
|
|
|
|
get "/latest"
|
|
|
|
|
|
2023-07-28 12:53:44 +01:00
|
|
|
expect(response.headers["X-Discourse-Cached"]).to eq("true")
|
|
|
|
|
expect(response.headers).not_to include("Discourse-GTM-Nonce-Placeholder")
|
|
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
2023-07-28 12:53:44 +01:00
|
|
|
report_only_script_src =
|
|
|
|
|
parse(response.headers["Content-Security-Policy-Report-Only"])["script-src"]
|
|
|
|
|
|
2023-07-11 15:24:36 -06:00
|
|
|
second_nonce = extract_nonce_from_script_src(script_src)
|
2023-07-28 12:53:44 +01:00
|
|
|
second_report_only_nonce = extract_nonce_from_script_src(report_only_script_src)
|
|
|
|
|
|
|
|
|
|
expect(second_nonce).to eq(second_report_only_nonce)
|
2023-07-11 15:24:36 -06:00
|
|
|
|
|
|
|
|
expect(first_nonce).not_to eq(second_nonce)
|
|
|
|
|
gtm_meta_tag = Nokogiri::HTML5.fragment(response.body).css("#data-google-tag-manager").first
|
|
|
|
|
expect(gtm_meta_tag["data-nonce"]).to eq(second_nonce)
|
2021-03-26 11:19:31 -04:00
|
|
|
end
|
|
|
|
|
|
2022-09-01 09:58:48 +01:00
|
|
|
it "when splash screen is enabled it adds the fingerprint to the policy" do
|
2022-06-30 21:22:00 +08:00
|
|
|
SiteSetting.content_security_policy = true
|
|
|
|
|
SiteSetting.splash_screen = true
|
|
|
|
|
|
|
|
|
|
get "/latest"
|
2022-09-01 09:58:48 +01:00
|
|
|
fingerprint = SplashScreenHelper.fingerprint
|
2022-06-30 21:22:00 +08:00
|
|
|
expect(response.headers).to include("Content-Security-Policy")
|
|
|
|
|
|
|
|
|
|
script_src = parse(response.headers["Content-Security-Policy"])["script-src"]
|
2022-09-01 09:58:48 +01:00
|
|
|
expect(script_src.to_s).to include(fingerprint)
|
|
|
|
|
expect(response.body).to include(SplashScreenHelper.inline_splash_screen_script)
|
2022-06-30 21:22:00 +08:00
|
|
|
end
|
|
|
|
|
|
2018-10-22 13:22:23 -04:00
|
|
|
def parse(csp_string)
|
|
|
|
|
csp_string
|
|
|
|
|
.split(";")
|
|
|
|
|
.map do |policy|
|
|
|
|
|
directive, *sources = policy.split
|
|
|
|
|
[directive, sources]
|
|
|
|
|
end
|
|
|
|
|
.to_h
|
|
|
|
|
end
|
2023-07-11 15:24:36 -06:00
|
|
|
|
|
|
|
|
def extract_nonce_from_script_src(script_src)
|
|
|
|
|
nonce = script_src.find { |src| src.match?(/\A'nonce-\h{32}'\z/) }[-33...-1]
|
|
|
|
|
expect(nonce).to be_present
|
|
|
|
|
nonce
|
|
|
|
|
end
|
2018-10-22 13:22:23 -04:00
|
|
|
end
|
2019-07-23 20:17:44 +03:00
|
|
|
|
|
|
|
|
it "can respond to a request with */* accept header" do
|
|
|
|
|
get "/", headers: { HTTP_ACCEPT: "*/*" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.body).to include("Discourse")
|
|
|
|
|
end
|
2020-05-12 09:13:20 +10:00
|
|
|
|
|
|
|
|
it "has canonical tag" do
|
|
|
|
|
get "/", headers: { HTTP_ACCEPT: "*/*" }
|
|
|
|
|
expect(response.body).to have_tag(
|
|
|
|
|
"link",
|
|
|
|
|
with: {
|
|
|
|
|
rel: "canonical",
|
|
|
|
|
href: "http://test.localhost/",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
get "/?query_param=true", headers: { HTTP_ACCEPT: "*/*" }
|
|
|
|
|
expect(response.body).to have_tag(
|
|
|
|
|
"link",
|
|
|
|
|
with: {
|
|
|
|
|
rel: "canonical",
|
|
|
|
|
href: "http://test.localhost/",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
get "/latest?page=2&additional_param=true", headers: { HTTP_ACCEPT: "*/*" }
|
|
|
|
|
expect(response.body).to have_tag(
|
|
|
|
|
"link",
|
|
|
|
|
with: {
|
|
|
|
|
rel: "canonical",
|
|
|
|
|
href: "http://test.localhost/latest?page=2",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
get "/404", headers: { HTTP_ACCEPT: "*/*" }
|
|
|
|
|
expect(response.body).to have_tag(
|
|
|
|
|
"link",
|
|
|
|
|
with: {
|
|
|
|
|
rel: "canonical",
|
|
|
|
|
href: "http://test.localhost/404",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
topic = create_post.topic
|
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}"
|
|
|
|
|
expect(response.body).to have_tag(
|
|
|
|
|
"link",
|
|
|
|
|
with: {
|
|
|
|
|
rel: "canonical",
|
|
|
|
|
href: "http://test.localhost/t/#{topic.slug}/#{topic.id}",
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
end
|
2020-07-22 17:30:26 +01:00
|
|
|
|
2021-11-25 16:58:39 -03:00
|
|
|
it "adds a noindex header if non-canonical indexing is disabled" do
|
|
|
|
|
SiteSetting.allow_indexing_non_canonical_urls = false
|
|
|
|
|
get "/"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to be_nil
|
|
|
|
|
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to be_nil
|
|
|
|
|
|
|
|
|
|
get "/categories"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to be_nil
|
|
|
|
|
|
|
|
|
|
topic = create_post.topic
|
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to be_nil
|
|
|
|
|
post = create_post(topic_id: topic.id)
|
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}/2"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to eq("noindex")
|
|
|
|
|
|
|
|
|
|
20.times { create_post(topic_id: topic.id) }
|
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}/21"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to eq("noindex")
|
|
|
|
|
get "/t/#{topic.slug}/#{topic.id}?page=2"
|
|
|
|
|
expect(response.headers["X-Robots-Tag"]).to be_nil
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "with default locale" do
|
2020-09-29 21:42:45 +02:00
|
|
|
before do
|
|
|
|
|
SiteSetting.default_locale = :fr
|
|
|
|
|
sign_in(Fabricate(:user))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
after { I18n.reload! }
|
|
|
|
|
|
|
|
|
|
context "with rate limits" do
|
2023-06-16 12:44:35 +10:00
|
|
|
before { RateLimiter.enable }
|
|
|
|
|
|
|
|
|
|
use_redis_snapshotting
|
2020-09-30 17:04:19 +02:00
|
|
|
|
2020-09-29 21:42:45 +02:00
|
|
|
it "serves a LimitExceeded error in the preferred locale" do
|
|
|
|
|
SiteSetting.max_likes_per_day = 1
|
|
|
|
|
post1 = Fabricate(:post)
|
|
|
|
|
post2 = Fabricate(:post)
|
|
|
|
|
override =
|
|
|
|
|
TranslationOverride.create(
|
|
|
|
|
locale: "fr",
|
|
|
|
|
translation_key: "rate_limiter.by_type.create_like",
|
|
|
|
|
value: "French LimitExceeded error message",
|
|
|
|
|
)
|
|
|
|
|
I18n.reload!
|
|
|
|
|
|
|
|
|
|
post "/post_actions.json",
|
|
|
|
|
params: {
|
|
|
|
|
id: post1.id,
|
|
|
|
|
post_action_type_id: PostActionType.types[:like],
|
|
|
|
|
}
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
|
|
post "/post_actions.json",
|
|
|
|
|
params: {
|
|
|
|
|
id: post2.id,
|
|
|
|
|
post_action_type_id: PostActionType.types[:like],
|
|
|
|
|
}
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.parsed_body["errors"].first).to eq(override.value)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "serves an InvalidParameters error with the default locale" do
|
|
|
|
|
override =
|
|
|
|
|
TranslationOverride.create(
|
|
|
|
|
locale: "fr",
|
|
|
|
|
translation_key: "invalid_params",
|
|
|
|
|
value: "French InvalidParameters error message",
|
|
|
|
|
)
|
|
|
|
|
I18n.reload!
|
|
|
|
|
|
|
|
|
|
get "/search.json", params: { q: "hello\0hello" }
|
|
|
|
|
expect(response.status).to eq(400)
|
|
|
|
|
expect(response.parsed_body["errors"].first).to eq(override.value)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-07-22 17:30:26 +01:00
|
|
|
describe "set_locale" do
|
|
|
|
|
# Using /bootstrap.json because it returns a locale-dependent value
|
|
|
|
|
def headers(locale)
|
|
|
|
|
{ HTTP_ACCEPT_LANGUAGE: locale }
|
|
|
|
|
end
|
|
|
|
|
|
2023-11-10 11:16:06 +00:00
|
|
|
def locale_scripts(body)
|
|
|
|
|
Nokogiri::HTML5
|
|
|
|
|
.parse(body)
|
|
|
|
|
.css('script[src*="assets/locales/"]')
|
|
|
|
|
.map { |script| script.attributes["src"].value }
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "with allow_user_locale disabled" do
|
|
|
|
|
context "when accept-language header differs from default locale" do
|
2020-07-22 17:30:26 +01:00
|
|
|
before do
|
|
|
|
|
SiteSetting.allow_user_locale = false
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with an anonymous user" do
|
|
|
|
|
it "uses the default locale" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("fr")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a logged in user" do
|
|
|
|
|
it "it uses the default locale" do
|
|
|
|
|
user = Fabricate(:user, locale: :fr)
|
|
|
|
|
sign_in(user)
|
|
|
|
|
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("fr")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "with set_locale_from_accept_language_header enabled" do
|
|
|
|
|
context "when accept-language header differs from default locale" do
|
2020-07-22 17:30:26 +01:00
|
|
|
before do
|
|
|
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
|
SiteSetting.set_locale_from_accept_language_header = true
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with an anonymous user" do
|
|
|
|
|
it "uses the locale from the headers" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("fr")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't leak after requests" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("fr")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a logged in user" do
|
2020-07-28 22:20:38 +01:00
|
|
|
let(:user) { Fabricate(:user, locale: :fr) }
|
|
|
|
|
|
2020-07-22 17:30:26 +01:00
|
|
|
before { sign_in(user) }
|
|
|
|
|
|
|
|
|
|
it "uses the user's preferred locale" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("fr")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/fr.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "serves a 404 page in the preferred locale" do
|
|
|
|
|
get "/missingroute", headers: headers("fr")
|
|
|
|
|
expect(response.status).to eq(404)
|
|
|
|
|
expected_title = I18n.t("page_not_found.title", locale: :fr)
|
|
|
|
|
expect(response.body).to include(CGI.escapeHTML(expected_title))
|
|
|
|
|
end
|
2020-07-28 22:20:38 +01:00
|
|
|
|
|
|
|
|
it "serves a RenderEmpty page in the preferred locale" do
|
|
|
|
|
get "/u/#{user.username}/preferences/interface"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.body).to have_tag("script", with: { src: "/assets/locales/fr.js" })
|
|
|
|
|
end
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "when the preferred locale includes a region" do
|
2020-07-22 17:30:26 +01:00
|
|
|
it "returns the locale and region separated by an underscore" do
|
|
|
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
|
SiteSetting.set_locale_from_accept_language_header = true
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("zh-CN")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/zh_CN.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "when accept-language header is not set" do
|
2020-07-22 17:30:26 +01:00
|
|
|
it "uses the site default locale" do
|
|
|
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: headers("")
|
2020-07-22 17:30:26 +01:00
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js")
|
2022-09-27 14:26:06 +05:30
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with set_locale_from_cookie enabled" do
|
|
|
|
|
context "when cookie locale differs from default locale" do
|
|
|
|
|
before do
|
|
|
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
|
SiteSetting.set_locale_from_cookie = true
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with an anonymous user" do
|
|
|
|
|
it "uses the locale from the cookie" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: { Cookie: "locale=es" }
|
2022-09-27 14:26:06 +05:30
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/es.js")
|
2022-09-27 14:26:06 +05:30
|
|
|
expect(I18n.locale.to_s).to eq(SiteSettings::DefaultsProvider::DEFAULT_LOCALE) # doesn't leak after requests
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when the preferred locale includes a region" do
|
|
|
|
|
it "returns the locale and region separated by an underscore" do
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: { Cookie: "locale=zh-CN" }
|
2022-09-27 14:26:06 +05:30
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/zh_CN.js")
|
2022-09-27 14:26:06 +05:30
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when locale cookie is not set" do
|
|
|
|
|
it "uses the site default locale" do
|
|
|
|
|
SiteSetting.allow_user_locale = true
|
|
|
|
|
SiteSetting.default_locale = "en"
|
|
|
|
|
|
2023-11-10 11:16:06 +00:00
|
|
|
get "/latest", headers: { Cookie: "" }
|
2022-09-27 14:26:06 +05:30
|
|
|
expect(response.status).to eq(200)
|
2023-11-10 11:16:06 +00:00
|
|
|
expect(locale_scripts(response.body)).to contain_exactly("/assets/locales/en.js")
|
2020-07-22 17:30:26 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-10-25 12:53:50 +01:00
|
|
|
|
|
|
|
|
describe "vary header" do
|
|
|
|
|
it "includes Vary:Accept on all requests where format is not explicit" do
|
|
|
|
|
# Rails default behaviour - include Vary:Accept when Accept is supplied
|
|
|
|
|
get "/latest", headers: { "Accept" => "application/json" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.headers["Vary"]).to eq("Accept")
|
|
|
|
|
|
|
|
|
|
# Discourse additional behaviour (see lib/vary_header.rb)
|
|
|
|
|
# Include Vary:Accept even when Accept is not supplied
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.headers["Vary"]).to eq("Accept")
|
|
|
|
|
|
|
|
|
|
# Not needed, because the path 'format' parameter overrides the Accept header
|
|
|
|
|
get "/latest.json"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(response.headers["Vary"]).to eq(nil)
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-11-17 23:27:30 +03:00
|
|
|
|
|
|
|
|
describe "Discourse-Rate-Limit-Error-Code header" do
|
2023-11-09 16:47:59 -06:00
|
|
|
fab!(:admin)
|
2021-11-17 23:27:30 +03:00
|
|
|
|
2023-06-16 12:44:35 +10:00
|
|
|
before { RateLimiter.enable }
|
|
|
|
|
|
|
|
|
|
use_redis_snapshotting
|
2021-11-17 23:27:30 +03:00
|
|
|
|
|
|
|
|
it "is included when API key is rate limited" do
|
|
|
|
|
global_setting :max_admin_api_reqs_per_minute, 1
|
|
|
|
|
api_key = ApiKey.create!(user_id: admin.id).key
|
|
|
|
|
get "/latest.json", headers: { "Api-Key": api_key, "Api-Username": admin.username }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
|
|
get "/latest.json", headers: { "Api-Key": api_key, "Api-Username": admin.username }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Discourse-Rate-Limit-Error-Code"]).to eq("admin_api_key_rate_limit")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is included when user API key is rate limited" do
|
|
|
|
|
global_setting :max_user_api_reqs_per_minute, 1
|
|
|
|
|
user_api_key =
|
|
|
|
|
UserApiKey.create!(user_id: admin.id, client_id: "", application_name: "discourseapp")
|
|
|
|
|
user_api_key.scopes =
|
|
|
|
|
UserApiKeyScope.all_scopes.keys.map do |name|
|
|
|
|
|
UserApiKeyScope.create!(name: name, user_api_key_id: user_api_key.id)
|
|
|
|
|
end
|
|
|
|
|
user_api_key.save!
|
|
|
|
|
|
|
|
|
|
get "/session/current.json", headers: { "User-Api-Key": user_api_key.key }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
|
|
get "/session/current.json", headers: { "User-Api-Key": user_api_key.key }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Discourse-Rate-Limit-Error-Code"]).to eq(
|
|
|
|
|
"user_api_key_limiter_60_secs",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
global_setting :max_user_api_reqs_per_minute, 100
|
|
|
|
|
global_setting :max_user_api_reqs_per_day, 1
|
|
|
|
|
|
|
|
|
|
get "/session/current.json", headers: { "User-Api-Key": user_api_key.key }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Discourse-Rate-Limit-Error-Code"]).to eq(
|
|
|
|
|
"user_api_key_limiter_1_day",
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-11-30 12:55:25 +03:00
|
|
|
|
|
|
|
|
describe "crawlers in slow_down_crawler_user_agents site setting" do
|
2023-06-16 12:44:35 +10:00
|
|
|
before { RateLimiter.enable }
|
|
|
|
|
|
|
|
|
|
use_redis_snapshotting
|
2021-11-30 12:55:25 +03:00
|
|
|
|
|
|
|
|
it "are rate limited" do
|
|
|
|
|
SiteSetting.slow_down_crawler_rate = 128
|
|
|
|
|
SiteSetting.slow_down_crawler_user_agents = "badcrawler|problematiccrawler"
|
|
|
|
|
now = Time.zone.now
|
|
|
|
|
freeze_time now
|
|
|
|
|
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam badcrawler" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam badcrawler" }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Retry-After"]).to eq("128")
|
|
|
|
|
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam problematiccrawler" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam problematiccrawler" }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Retry-After"]).to eq("128")
|
|
|
|
|
|
|
|
|
|
freeze_time now + 100.seconds
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam badcrawler" }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Retry-After"]).to eq("28")
|
|
|
|
|
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam problematiccrawler" }
|
|
|
|
|
expect(response.status).to eq(429)
|
|
|
|
|
expect(response.headers["Retry-After"]).to eq("28")
|
|
|
|
|
|
|
|
|
|
freeze_time now + 150.seconds
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam badcrawler" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
|
|
|
|
|
get "/", headers: { "HTTP_USER_AGENT" => "iam problematiccrawler" }
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-06-13 11:10:21 -06:00
|
|
|
|
|
|
|
|
describe "#banner_json" do
|
|
|
|
|
let(:admin) { Fabricate(:admin) }
|
|
|
|
|
let(:user) { Fabricate(:user) }
|
2023-11-09 16:47:59 -06:00
|
|
|
fab!(:banner_topic)
|
2022-06-13 11:10:21 -06:00
|
|
|
fab!(:p1) { Fabricate(:post, topic: banner_topic, raw: "A banner topic") }
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
admin # to skip welcome wizard at home page `/`
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "with login_required" do
|
2022-06-13 11:10:21 -06:00
|
|
|
before { SiteSetting.login_required = true }
|
|
|
|
|
it "does not include banner info for anonymous users" do
|
|
|
|
|
get "/login"
|
|
|
|
|
|
|
|
|
|
expect(response.body).to have_tag("div#data-preloaded") do |element|
|
|
|
|
|
json = JSON.parse(element.current_scope.attribute("data-preloaded").value)
|
|
|
|
|
expect(json["banner"]).to eq("{}")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "includes banner info for logged-in users" do
|
|
|
|
|
sign_in(user)
|
|
|
|
|
get "/"
|
|
|
|
|
|
|
|
|
|
expect(response.body).to have_tag("div#data-preloaded") do |element|
|
|
|
|
|
json = JSON.parse(element.current_scope.attribute("data-preloaded").value)
|
|
|
|
|
expect(JSON.parse(json["banner"])["html"]).to eq("<p>A banner topic</p>")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 18:14:14 +02:00
|
|
|
context "with login not required" do
|
2022-06-13 11:10:21 -06:00
|
|
|
before { SiteSetting.login_required = false }
|
|
|
|
|
it "does include banner info for anonymous users" do
|
|
|
|
|
get "/login"
|
|
|
|
|
|
|
|
|
|
expect(response.body).to have_tag("div#data-preloaded") do |element|
|
|
|
|
|
json = JSON.parse(element.current_scope.attribute("data-preloaded").value)
|
|
|
|
|
expect(JSON.parse(json["banner"])["html"]).to eq("<p>A banner topic</p>")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-10-11 20:11:44 -03:00
|
|
|
|
|
|
|
|
describe "preload Link header" do
|
|
|
|
|
context "with GlobalSetting.preload_link_header" do
|
|
|
|
|
before { global_setting :preload_link_header, true }
|
|
|
|
|
|
|
|
|
|
it "should have the Link header with assets on full page requests" do
|
|
|
|
|
get("/latest")
|
|
|
|
|
expect(response.headers).to include("Link")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "shouldn't have the Link header on xhr api requests" do
|
|
|
|
|
get("/latest.json")
|
|
|
|
|
expect(response.headers).not_to include("Link")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "without GlobalSetting.preload_link_header" do
|
|
|
|
|
before { global_setting :preload_link_header, false }
|
|
|
|
|
|
|
|
|
|
it "shouldn't have the Link header with assets on full page requests" do
|
|
|
|
|
get("/latest")
|
|
|
|
|
expect(response.headers).not_to include("Link")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "shouldn't have the Link header on xhr api requests" do
|
|
|
|
|
get("/latest.json")
|
|
|
|
|
expect(response.headers).not_to include("Link")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2023-07-03 11:30:26 +10:00
|
|
|
|
|
|
|
|
describe "preloading data" do
|
|
|
|
|
def preloaded_json
|
|
|
|
|
JSON.parse(
|
|
|
|
|
Nokogiri::HTML5.fragment(response.body).css("div#data-preloaded").first["data-preloaded"],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when user is anon" do
|
|
|
|
|
it "preloads the relevant JSON data" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(preloaded_json.keys).to match_array(
|
|
|
|
|
[
|
|
|
|
|
"site",
|
|
|
|
|
"siteSettings",
|
|
|
|
|
"customHTML",
|
|
|
|
|
"banner",
|
|
|
|
|
"customEmoji",
|
|
|
|
|
"isReadOnly",
|
|
|
|
|
"isStaffWritesOnly",
|
|
|
|
|
"activatedThemes",
|
|
|
|
|
"#{TopicList.new("latest", Fabricate(:anonymous), []).preload_key}",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when user is regular user" do
|
2023-11-09 16:47:59 -06:00
|
|
|
fab!(:user)
|
2023-07-03 11:30:26 +10:00
|
|
|
|
|
|
|
|
before { sign_in(user) }
|
|
|
|
|
|
|
|
|
|
it "preloads the relevant JSON data" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(preloaded_json.keys).to match_array(
|
|
|
|
|
[
|
|
|
|
|
"site",
|
|
|
|
|
"siteSettings",
|
|
|
|
|
"customHTML",
|
|
|
|
|
"banner",
|
|
|
|
|
"customEmoji",
|
|
|
|
|
"isReadOnly",
|
|
|
|
|
"isStaffWritesOnly",
|
|
|
|
|
"activatedThemes",
|
|
|
|
|
"#{TopicList.new("latest", Fabricate(:anonymous), []).preload_key}",
|
|
|
|
|
"currentUser",
|
|
|
|
|
"topicTrackingStates",
|
|
|
|
|
"topicTrackingStateMeta",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when user is admin" do
|
|
|
|
|
fab!(:user) { Fabricate(:admin) }
|
|
|
|
|
|
|
|
|
|
before { sign_in(user) }
|
|
|
|
|
|
|
|
|
|
it "preloads the relevant JSON data" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
expect(preloaded_json.keys).to match_array(
|
|
|
|
|
[
|
|
|
|
|
"site",
|
|
|
|
|
"siteSettings",
|
|
|
|
|
"customHTML",
|
|
|
|
|
"banner",
|
|
|
|
|
"customEmoji",
|
|
|
|
|
"isReadOnly",
|
|
|
|
|
"isStaffWritesOnly",
|
|
|
|
|
"activatedThemes",
|
|
|
|
|
"#{TopicList.new("latest", Fabricate(:anonymous), []).preload_key}",
|
|
|
|
|
"currentUser",
|
|
|
|
|
"topicTrackingStates",
|
|
|
|
|
"topicTrackingStateMeta",
|
|
|
|
|
"fontMap",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "generates a fontMap" do
|
|
|
|
|
get "/latest"
|
|
|
|
|
expect(response.status).to eq(200)
|
|
|
|
|
font_map = JSON.parse(preloaded_json["fontMap"])
|
|
|
|
|
expect(font_map.keys).to match_array(
|
|
|
|
|
DiscourseFonts.fonts.filter { |f| f[:variants].present? }.map { |f| f[:key] },
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-03-27 18:04:40 +05:30
|
|
|
end
|