mirror of
https://github.com/discourse/discourse.git
synced 2026-08-06 13:08:40 +08:00
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.
194 lines
6.7 KiB
Ruby
Vendored
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
|