mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 18:13:39 +08:00
ce53152e53
Our theme system injects a magical `settings` object at the top of themes JS modules to allow theme authors to access the settings as configured by admins in the UI. Within this `settings` object, there are a couple of special objects `theme_uploads` and `theme_uploads_local` that contain URLs for all the assets/uploads that the theme has. For test modules/files, the theme system also injects a `settings` object at the top of tests modules, but it's not the same object as the object that's injected in non-test files. The difference is that in tests we want the settings to have their default values as opposed to any custom values that may exist in the site's database. This ensures that test results are consistent no matter the site that runs them. However, the `settings` object in tests files currently doesn't have the special objects `theme_uploads` and `theme_uploads_local` which means that if a theme includes an asset that's lazy-loaded, it's not possible to write tests for anything that depends on the lazy-loaded asset because the theme will not be able to load the asset during the tests since `theme_uploads_local` and `theme_uploads` don't exist. This PR adds these special objects inside the `settings` object for test files. Internal topic: t/71825/52.
211 lines
7.6 KiB
Ruby
211 lines
7.6 KiB
Ruby
# 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) { ThemeField.create!(theme: theme, target_id: 0, name: "header", value: "<a>html</a>") }
|
|
let(:javascript_cache) { JavascriptCache.create!(content: 'console.log("hello");', theme_field: theme_field) }
|
|
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
|
|
digest = SecureRandom.hex(20)
|
|
javascript_cache.update(digest: digest)
|
|
get "/theme-javascripts/#{digest}.js"
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to eq('console.log("hello");')
|
|
|
|
digest = SecureRandom.hex(20)
|
|
javascript_cache.update(digest: digest, source_map: '{fakeSourceMap: true}')
|
|
get "/theme-javascripts/#{digest}.js"
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to eq <<~JS
|
|
console.log("hello");
|
|
//# sourceMappingURL=#{digest}.map?__ws=test.localhost
|
|
JS
|
|
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)
|
|
|
|
digest = SecureRandom.hex(20)
|
|
javascript_cache.update(digest: digest, source_map: '{fakeSourceMap: true}')
|
|
get "/theme-javascripts/#{digest}.map"
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to eq("{fakeSourceMap: true}")
|
|
|
|
javascript_cache.destroy
|
|
get "/theme-javascripts/#{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 "forces theme settings default values" do
|
|
component.update_setting(:num_setting, 643)
|
|
_, digest = component.baked_js_tests_with_digest
|
|
|
|
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
|
|
expect(response.body).to include("require(\"discourse/lib/theme-settings-store\").registerSettings(#{component.id}, {\"num_setting\":5}, { force: true });")
|
|
expect(response.body).to include("assert.ok(true);")
|
|
end
|
|
|
|
it "includes theme uploads URLs in the settings object" do
|
|
SiteSetting.authorized_extensions = "*"
|
|
js_file = Tempfile.new(["vendorlib", ".js"])
|
|
js_file.write("console.log(123);\n")
|
|
js_file.rewind
|
|
js_upload = UploadCreator.new(js_file, "vendorlib.js").create_for(Discourse::SYSTEM_USER_ID)
|
|
component.set_field(
|
|
type: :theme_upload_var,
|
|
target: :common,
|
|
name: "vendorlib",
|
|
upload_id: js_upload.id
|
|
)
|
|
component.save!
|
|
_, digest = component.baked_js_tests_with_digest
|
|
|
|
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
|
|
expect(response.body).to include(
|
|
"require(\"discourse/lib/theme-settings-store\").registerSettings(" +
|
|
"#{component.id}, {\"num_setting\":5,\"theme_uploads\":{\"vendorlib\":" +
|
|
"\"/uploads/default/test_#{ENV['TEST_ENV_NUMBER']}/original/1X/#{js_upload.sha1}.js\"},\"theme_uploads_local\":{\"vendorlib\":" +
|
|
"\"/theme-javascripts/#{js_upload.sha1}.js?__ws=test.localhost\"}}, { force: true });"
|
|
)
|
|
expect(response.body).to include("assert.ok(true);")
|
|
ensure
|
|
js_file&.close
|
|
js_file&.unlink
|
|
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
|
|
|
|
it "includes inline sourcemap" do
|
|
ThemeJavascriptCompiler.enable_terser!
|
|
content, digest = component.baked_js_tests_with_digest
|
|
get "/theme-javascripts/tests/#{component.id}-#{digest}.js"
|
|
expect(response.status).to eq(200)
|
|
expect(response.body).to include("//# sourceMappingURL=data:application/json;base64,")
|
|
end
|
|
end
|
|
end
|