0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-06 13:08:40 +08:00
discourse/spec/requests/theme_javascripts_controller_spec.rb
David Taylor d42c648fc7
PERF: Move theme settings from js bundle to preload data (#40137)
This means that we no longer need to recompile theme JS bundles when
theme settings are changed. It has parity with site settings, which are
already included in the preload data. Theme#cached_settings is already
cached in-memory, so performance impact is minimal.

Since theme JS bundles are now only dependent on `extra_js` ThemeFields,
we can make their caching much more stricter, meaning that they don't
need to be recompiled on every single `theme.save!`. Now they will only
be invalidated when extra_js fields actually change, or when the theme
compiler_version changes.
2026-06-08 16:04:56 +01:00

194 lines
6.7 KiB
Ruby
Vendored

# frozen_string_literal: true
RSpec.describe ThemeJavascriptsController do
include ActiveSupport::Testing::TimeHelpers
before { ThemeJavascriptCompiler.disable_terser! }
after { ThemeJavascriptCompiler.enable_terser! }
def clear_disk_cache
if Dir.exist?(ThemeJavascriptsController::DISK_CACHE_PATH)
`rm -rf #{ThemeJavascriptsController::DISK_CACHE_PATH}`
end
end
let!(:theme) { Fabricate(:theme) }
let(:theme_field) do
ThemeField.create!(theme: theme, target_id: 0, name: "header", value: "<a>html</a>")
end
let(:javascript_cache) do
JavascriptCache.create!(content: 'console.log("hello");', theme_field: theme_field)
end
before { clear_disk_cache }
after { clear_disk_cache }
describe "#show" do
def update_digest_and_get(digest)
# actually set digest to make sure 404 is raised by router
javascript_cache.update(digest: digest)
get "/theme-javascripts/#{digest}.js"
end
it "only accepts 40-char hexadecimal digest name" do
update_digest_and_get("0123456789abcdefabcd0123456789abcdefabcd")
expect(response.status).to eq(200)
update_digest_and_get("0123456789abcdefabcd0123456789abcdefabc")
expect(response.status).to eq(404)
update_digest_and_get("gggggggggggggggggggggggggggggggggggggggg")
expect(response.status).to eq(404)
update_digest_and_get("0123456789abcdefabc_0123456789abcdefabcd")
expect(response.status).to eq(404)
update_digest_and_get("0123456789abcdefabc-0123456789abcdefabcd")
expect(response.status).to eq(404)
update_digest_and_get("../../Gemfile")
expect(response.status).to eq(404)
end
it "considers the database record as the source of truth" do
clear_disk_cache
get "/theme-javascripts/#{javascript_cache.digest}.js"
expect(response.status).to eq(200)
expect(response.body).to eq(javascript_cache.content)
expect(response.headers["Content-Length"]).to eq(javascript_cache.content.bytesize.to_s)
javascript_cache.destroy!
get "/theme-javascripts/#{javascript_cache.digest}.js"
expect(response.status).to eq(404)
end
it "adds sourceMappingUrl if there is a source map" do
get "/theme-javascripts/#{javascript_cache.digest}.js"
expect(response.status).to eq(200)
expect(response.body).to eq('console.log("hello");')
javascript_cache.update(source_map: "{fakeSourceMap: true}")
get "/theme-javascripts/#{javascript_cache.digest}.js"
expect(response.status).to eq(200)
expect(response.body).to eq <<~JS
console.log("hello");
//# sourceMappingURL=#{javascript_cache.digest}.map?__ws=test.localhost
JS
end
it "ignores Accept header and does not return Vary header" do
js_cache_url = "/theme-javascripts/#{javascript_cache.digest}.js"
get js_cache_url
expect(response.status).to eq(200)
expect(response.headers["Content-Type"]).to eq("text/javascript")
expect(response.headers["Vary"]).to eq(nil)
get js_cache_url, headers: { "Accept" => "text/html" }
expect(response.status).to eq(200)
expect(response.headers["Content-Type"]).to eq("text/javascript")
expect(response.headers["Vary"]).to eq(nil)
get js_cache_url, headers: { "Accept" => "invalidcontenttype" }
expect(response.status).to eq(200)
expect(response.headers["Content-Type"]).to eq("text/javascript")
expect(response.headers["Vary"]).to eq(nil)
end
end
describe "#show_map" do
it "returns a source map when present" do
get "/theme-javascripts/#{javascript_cache.digest}.map"
expect(response.status).to eq(404)
javascript_cache.update(source_map: "{fakeSourceMap: true}")
get "/theme-javascripts/#{javascript_cache.digest}.map"
expect(response.status).to eq(200)
expect(response.body).to eq("{fakeSourceMap: true}")
javascript_cache.destroy
get "/theme-javascripts/#{javascript_cache.digest}.map"
expect(response.status).to eq(404)
end
end
describe "#show_tests" do
let(:component) { Fabricate(:theme, component: true, name: "enabled-component") }
let!(:tests_field) do
field =
component.set_field(
target: :tests_js,
type: :js,
name: "acceptance/some-test.js",
value: "assert.ok(true);",
)
component.save!
field
end
before do
ThemeField.create!(
theme: component,
target_id: Theme.targets[:settings],
name: "yaml",
value: "num_setting: 5",
)
component.save!
end
it "serves the bundled test file" do
_, digest = component.baked_js_tests_with_digest
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
expect(response.body).to include("assert.ok(true);")
end
it "responds with 404 if digest is not a 40 chars hex" do
digest = Rack::Utils.escape("../../../../../../../../../../etc/passwd").gsub(".", "%2E")
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
expect(response.status).to eq(404)
get "/theme-javascripts/tests/#{component.id}-abc123.js"
expect(response.status).to eq(404)
end
it "responds with 404 if theme does not exist" do
get "/theme-javascripts/tests/#{Theme.maximum(:id) + 1}-#{SecureRandom.hex(20)}.js"
expect(response.status).to eq(404)
end
it "responds with 304 if tests digest has not changed" do
content, digest = component.baked_js_tests_with_digest
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
last_modified = Time.rfc2822(response.headers["Last-Modified"])
expect(response.status).to eq(200)
expect(response.headers["Content-Length"].to_i).to eq(content.size)
get "/theme-javascripts/tests/#{component.id}-#{digest}.js",
headers: {
"If-Modified-Since" => (last_modified + 10.seconds).rfc2822,
}
expect(response.status).to eq(304)
end
it "responds with 404 to requests with old digests" do
_, old_digest = component.baked_js_tests_with_digest
get "/theme-javascripts/tests/#{component.id}-#{old_digest}.js"
expect(response.status).to eq(200)
expect(response.body).to include("assert.ok(true);")
tests_field.update!(value: "assert.ok(343434);")
tests_field.invalidate_baked!
_, digest = component.baked_js_tests_with_digest
expect(old_digest).not_to eq(digest)
get "/theme-javascripts/tests/#{component.id}-#{old_digest}.js"
expect(response.status).to eq(404)
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
expect(response.status).to eq(200)
expect(response.body).to include("assert.ok(343434);")
end
end
end