discourse/spec/requests/admin/themes_controller_spec.rb

1034 lines
33 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Admin::ThemesController do
fab!(:admin) { Fabricate(:admin) }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:user) { Fabricate(:user) }
let! :repo do
setup_git_repo("about.json" => { name: "discourse-branch-header" }.to_json)
end
let! :repo_url do
MockGitImporter.register("https://github.com/discourse/discourse-brand-header.git", repo)
end
around(:each) { |group| MockGitImporter.with_mock { group.run } }
describe "#generate_key_pair" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "can generate key pairs" do
post "/admin/themes/generate_key_pair.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["private_key"]).to eq(nil)
expect(json["public_key"]).to include("ssh-rsa ")
expect(Discourse.redis.get("ssh_key_#{json["public_key"]}")).not_to eq(nil)
end
end
shared_examples "key pair generation not allowed" do
it "prevents key pair generation with a 404 response" do
post "/admin/themes/generate_key_pair.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "key pair generation not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "key pair generation not allowed"
end
end
describe "#upload_asset" do
let(:file) { file_from_fixtures("fake.woff2", "woff2") }
let(:filename) { File.basename(file) }
let(:upload) { Rack::Test::UploadedFile.new(file) }
context "when logged in as an admin" do
before { sign_in(admin) }
it "can create a theme upload" do
post "/admin/themes/upload_asset.json", params: { file: upload }
expect(response.status).to eq(201)
upload = Upload.find_by(original_filename: filename)
expect(upload.id).not_to be_nil
expect(response.parsed_body["upload_id"]).to eq(upload.id)
end
context "when trying to upload an existing file" do
let(:uploaded_file) { Upload.find_by(original_filename: filename) }
let(:response_json) { response.parsed_body }
before do
post "/admin/themes/upload_asset.json", params: { file: upload }
expect(response.status).to eq(201)
end
it "reuses the original upload" do
expect(response.status).to eq(201)
expect(response_json["upload_id"]).to eq(uploaded_file.id)
end
end
end
shared_examples "theme asset upload not allowed" do
it "prevents theme asset upload with a 404 response" do
expect do
post "/admin/themes/upload_asset.json", params: { file: upload }
end.not_to change { Upload.count }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme asset upload not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme asset upload not allowed"
end
end
describe "#export" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "exports correctly" do
theme = Fabricate(:theme, name: "Awesome Theme")
theme.set_field(target: :common, name: :scss, value: ".body{color: black;}")
theme.set_field(target: :desktop, name: :after_header, value: "<b>test</b>")
theme.set_field(
target: :extra_js,
name: "discourse/controller/blah",
value: 'console.log("test");',
)
theme.save!
get "/admin/customize/themes/#{theme.id}/export"
expect(response.status).to eq(200)
# Save the output in a temp file (automatically cleaned up)
file = Tempfile.new("archive.zip")
file.write(response.body)
file.rewind
uploaded_file = Rack::Test::UploadedFile.new(file.path, "application/zip")
# Now import it again
expect do
post "/admin/themes/import.json", params: { theme: uploaded_file }
expect(response.status).to eq(201)
end.to change { Theme.count }.by (1)
json = response.parsed_body
expect(json["theme"]["name"]).to eq("Awesome Theme")
expect(json["theme"]["theme_fields"].length).to eq(3)
end
end
shared_examples "theme export not allowed" do
it "prevents theme export with a 404 response" do
theme = Fabricate(:theme, name: "Awesome Theme")
get "/admin/customize/themes/#{theme.id}/export"
expect(response.status).to eq(404)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme export not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme export not allowed"
end
end
describe "#import" do
let(:theme_json_file) do
Rack::Test::UploadedFile.new(
file_from_fixtures("sam-s-simple-theme.dcstyle.json", "json"),
"application/json",
)
end
let(:theme_archive) do
Rack::Test::UploadedFile.new(
file_from_fixtures("discourse-test-theme.zip", "themes"),
"application/zip",
)
end
let(:image) { file_from_fixtures("logo.png") }
context "when logged in as an admin" do
before { sign_in(admin) }
context "when theme allowlist mode is enabled" do
before do
global_setting :allowed_theme_repos,
"https://github.com/discourse/discourse-brand-header.git"
end
it "allows allowlisted imports" do
expect(Theme.allowed_remote_theme_ids.length).to eq(0)
post "/admin/themes/import.json",
params: {
remote: " https://github.com/discourse/discourse-brand-header.git ",
}
expect(Theme.allowed_remote_theme_ids.length).to eq(1)
expect(response.status).to eq(201)
end
it "prevents adding disallowed themes" do
RemoteTheme.stubs(:import_theme)
remote = " https://bad.com/discourse/discourse-brand-header.git "
post "/admin/themes/import.json", params: { remote: remote }
expect(response.status).to eq(403)
expect(response.parsed_body["errors"]).to include(
I18n.t("themes.import_error.not_allowed_theme", { repo: remote.strip }),
)
end
it "bans json file import" do
post "/admin/themes/import.json", params: { theme: theme_json_file }
expect(response.status).to eq(403)
end
end
it "can import a theme from Git" do
RemoteTheme.stubs(:import_theme)
post "/admin/themes/import.json",
params: {
remote: " https://github.com/discourse/discourse-brand-header.git ",
}
expect(response.status).to eq(201)
end
it "fails to import with a failing status" do
post "/admin/themes/import.json", params: { remote: "non-existant" }
expect(response.status).to eq(422)
end
it "can lookup a private key by public key" do
Discourse.redis.setex("ssh_key_abcdef", 1.hour, "rsa private key")
ThemeStore::GitImporter.any_instance.stubs(:import!)
RemoteTheme.stubs(:extract_theme_info).returns(
"name" => "discourse-brand-header",
"component" => true,
)
RemoteTheme.any_instance.stubs(:update_from_remote)
post "/admin/themes/import.json",
params: {
remote: " https://github.com/discourse/discourse-brand-header.git ",
public_key: "abcdef",
}
expect(RemoteTheme.last.private_key).to eq("rsa private key")
expect(response.status).to eq(201)
end
it "imports a theme" do
post "/admin/themes/import.json", params: { theme: theme_json_file }
expect(response.status).to eq(201)
json = response.parsed_body
expect(json["theme"]["name"]).to eq("Sam's Simple Theme")
expect(json["theme"]["theme_fields"].length).to eq(2)
expect(json["theme"]["auto_update"]).to eq(false)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
it "can fail if theme is not accessible" do
post "/admin/themes/import.json",
params: {
remote: "git@github.com:discourse/discourse-inexistent-theme.git",
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to contain_exactly(I18n.t("themes.import_error.git"))
end
it "can force install theme" do
post "/admin/themes/import.json",
params: {
remote: "git@github.com:discourse/discourse-inexistent-theme.git",
force: true,
}
expect(response.status).to eq(201)
expect(response.parsed_body["theme"]["name"]).to eq("discourse-inexistent-theme")
end
it "fails to import with an error if uploads are not allowed" do
SiteSetting.theme_authorized_extensions = "nothing"
expect do post "/admin/themes/import.json", params: { theme: theme_archive } end.to change {
Theme.count
}.by (0)
expect(response.status).to eq(422)
end
it "imports a theme from an archive" do
_existing_theme = Fabricate(:theme, name: "Header Icons")
expect do post "/admin/themes/import.json", params: { theme: theme_archive } end.to change {
Theme.count
}.by (1)
expect(response.status).to eq(201)
json = response.parsed_body
expect(json["theme"]["name"]).to eq("Header Icons")
expect(json["theme"]["theme_fields"].length).to eq(5)
expect(json["theme"]["auto_update"]).to eq(false)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
it "updates an existing theme from an archive by name" do
# Old theme CLI method, remove Jan 2020
_existing_theme = Fabricate(:theme, name: "Header Icons")
expect do
post "/admin/themes/import.json", params: { bundle: theme_archive }
end.to change { Theme.count }.by (0)
expect(response.status).to eq(201)
json = response.parsed_body
expect(json["theme"]["name"]).to eq("Header Icons")
expect(json["theme"]["theme_fields"].length).to eq(5)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
it "updates an existing theme from an archive by id" do
# Used by theme CLI
_existing_theme = Fabricate(:theme, name: "Header Icons")
other_existing_theme = Fabricate(:theme, name: "Some other name")
messages =
MessageBus.track_publish do
expect do
post "/admin/themes/import.json",
params: {
bundle: theme_archive,
theme_id: other_existing_theme.id,
}
end.to change { Theme.count }.by (0)
end
expect(response.status).to eq(201)
json = response.parsed_body
# Ensure only one refresh message is sent.
# More than 1 is wasteful, and can trigger unusual race conditions in the client
# If this test fails, it probably means `theme.save` is being called twice - check any 'autosave' relations
file_change_messages = messages.filter { |m| m[:channel] == "/file-change" }
expect(file_change_messages.count).to eq(1)
expect(json["theme"]["name"]).to eq("Some other name")
expect(json["theme"]["id"]).to eq(other_existing_theme.id)
expect(json["theme"]["theme_fields"].length).to eq(5)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
it "creates a new theme when id specified as nil" do
# Used by theme CLI
existing_theme = Fabricate(:theme, name: "Header Icons")
expect do
post "/admin/themes/import.json", params: { bundle: theme_archive, theme_id: nil }
end.to change { Theme.count }.by (1)
expect(response.status).to eq(201)
json = response.parsed_body
expect(json["theme"]["name"]).to eq("Header Icons")
expect(json["theme"]["id"]).not_to eq(existing_theme.id)
expect(json["theme"]["theme_fields"].length).to eq(5)
expect(json["theme"]["auto_update"]).to eq(false)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
end
shared_examples "theme import not allowed" do
it "prevents theme import with a 404 response" do
post "/admin/themes/import.json", params: { theme: theme_json_file }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme import not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme import not allowed"
end
end
describe "#index" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "correctly returns themes" do
ColorScheme.destroy_all
Theme.destroy_all
theme = Fabricate(:theme)
theme.set_field(target: :common, name: :scss, value: ".body{color: black;}")
theme.set_field(target: :desktop, name: :after_header, value: "<b>test</b>")
theme.remote_theme =
RemoteTheme.new(
remote_url: "awesome.git",
remote_version: "7",
local_version: "8",
remote_updated_at: Time.zone.now,
)
theme.save!
# this will get serialized as well
ColorScheme.create_from_base(name: "test", colors: [])
get "/admin/themes.json"
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["extras"]["color_schemes"].length).to eq(1)
theme_json = json["themes"].find { |t| t["id"] == theme.id }
expect(theme_json["theme_fields"].length).to eq(2)
expect(theme_json["remote_theme"]["remote_version"]).to eq("7")
end
end
shared_examples "themes inaccessible" do
it "denies access with a 404 response" do
get "/admin/themes.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "themes inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "themes inaccessible"
end
end
describe "#create" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "creates a theme" do
post "/admin/themes.json",
params: {
theme: {
name: "my test name",
theme_fields: [name: "scss", target: "common", value: "body{color: red;}"],
},
}
expect(response.status).to eq(201)
json = response.parsed_body
expect(json["theme"]["theme_fields"].length).to eq(1)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
end
shared_examples "theme creation not allowed" do
it "prevents creation with a 404 response" do
expect do
post "/admin/themes.json",
params: {
theme: {
name: "my test name",
theme_fields: [name: "scss", target: "common", value: "body{color: red;}"],
},
}
end.not_to change { Theme.count }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme creation not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme creation not allowed"
end
end
describe "#update" do
let!(:theme) { Fabricate(:theme) }
context "when logged in as an admin" do
before { sign_in(admin) }
it "returns the right response when an invalid id is given" do
put "/admin/themes/99999.json"
expect(response.status).to eq(400)
end
it "can change default theme" do
SiteSetting.default_theme_id = -1
put "/admin/themes/#{theme.id}.json", params: { id: theme.id, theme: { default: true } }
expect(response.status).to eq(200)
expect(SiteSetting.default_theme_id).to eq(theme.id)
end
it "can unset default theme" do
SiteSetting.default_theme_id = theme.id
put "/admin/themes/#{theme.id}.json", params: { theme: { default: false } }
expect(response.status).to eq(200)
expect(SiteSetting.default_theme_id).to eq(-1)
end
context "when theme allowlist mode is enabled" do
before do
global_setting :allowed_theme_repos, " https://magic.com/repo.git, https://x.com/git"
end
it "unconditionally bans theme_fields from updating" do
r = RemoteTheme.create!(remote_url: "https://magic.com/repo.git")
theme.update!(remote_theme_id: r.id)
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
name: "my test name",
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "scss", target: "desktop", value: "body{color: blue;}" },
],
},
}
expect(response.status).to eq(403)
end
end
it "updates a theme" do
theme.set_field(target: :common, name: :scss, value: ".body{color: black;}")
theme.save
child_theme = Fabricate(:theme, component: true)
upload =
UploadCreator.new(file_from_fixtures("logo.png"), "logo.png").create_for(
Discourse.system_user.id,
)
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
child_theme_ids: [child_theme.id],
name: "my test name",
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "scss", target: "desktop", value: "body{color: blue;}" },
{ name: "bob", target: "common", value: "", type_id: 2, upload_id: upload.id },
],
},
}
expect(response.status).to eq(200)
json = response.parsed_body
fields = json["theme"]["theme_fields"].sort { |a, b| a["value"] <=> b["value"] }
expect(fields[0]["value"]).to eq("")
expect(fields[0]["upload_id"]).to eq(upload.id)
expect(fields[1]["value"]).to eq("body{color: blue;}")
expect(fields.length).to eq(2)
expect(json["theme"]["child_themes"].length).to eq(1)
expect(UserHistory.where(action: UserHistory.actions[:change_theme]).count).to eq(1)
end
it "prevents theme update when using ember css selectors" do
child_theme = Fabricate(:theme, component: true)
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
child_theme_ids: [child_theme.id],
name: "my test name",
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "scss", target: "desktop", value: ".ember-view{color: blue;}" },
],
},
}
expect(response.status).to eq(200)
json = response.parsed_body
fields = json["theme"]["theme_fields"].sort { |a, b| a["value"] <=> b["value"] }
expect(fields[0]["error"]).to eq(I18n.t("themes.ember_selector_error"))
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
child_theme_ids: [child_theme.id],
name: "my test name",
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "scss", target: "desktop", value: "#ember392{color: blue;}" },
],
},
}
expect(response.status).to eq(200)
json = response.parsed_body
fields = json["theme"]["theme_fields"].sort { |a, b| a["value"] <=> b["value"] }
expect(fields[0]["error"]).to eq(I18n.t("themes.ember_selector_error"))
end
it "blocks remote theme fields from being locally edited" do
r = RemoteTheme.create!(remote_url: "https://magic.com/repo.git")
theme.update!(remote_theme_id: r.id)
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "header", target: "common", value: "filename.jpg", upload_id: 4 },
],
},
}
expect(response.status).to eq(403)
end
it "allows zip-imported theme fields to be locally edited" do
r = RemoteTheme.create!(remote_url: "")
theme.update!(remote_theme_id: r.id)
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
theme_fields: [
{ name: "scss", target: "common", value: "" },
{ name: "header", target: "common", value: "filename.jpg", upload_id: 4 },
],
},
}
expect(response.status).to eq(200)
end
it "updates a child theme" do
child_theme = Fabricate(:theme, component: true)
put "/admin/themes/#{child_theme.id}.json",
params: {
theme: {
parent_theme_ids: [theme.id],
},
}
expect(child_theme.parent_themes).to eq([theme])
end
it "can update translations" do
theme.set_field(
target: :translations,
name: :en,
value: { en: { somegroup: { somestring: "defaultstring" } } }.deep_stringify_keys.to_yaml,
)
theme.save!
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
translations: {
"somegroup.somestring" => "overriddenstring",
},
},
}
# Response correct
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["theme"]["translations"][0]["value"]).to eq("overriddenstring")
# Database correct
theme.reload
expect(theme.theme_translation_overrides.count).to eq(1)
expect(theme.theme_translation_overrides.first.translation_key).to eq(
"somegroup.somestring",
)
# Set back to default
put "/admin/themes/#{theme.id}.json",
params: {
theme: {
translations: {
"somegroup.somestring" => "defaultstring",
},
},
}
# Response correct
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["theme"]["translations"][0]["value"]).to eq("defaultstring")
# Database correct
theme.reload
expect(theme.theme_translation_overrides.count).to eq(0)
end
it "checking for updates saves the remote_theme record" do
theme.remote_theme =
RemoteTheme.create!(
remote_url: "http://discourse.org",
remote_version: "a",
local_version: "a",
commits_behind: 0,
)
theme.save!
ThemeStore::GitImporter.any_instance.stubs(:import!)
ThemeStore::GitImporter.any_instance.stubs(:commits_since).returns(["b", 1])
put "/admin/themes/#{theme.id}.json", params: { theme: { remote_check: true } }
theme.reload
expect(theme.remote_theme.remote_version).to eq("b")
expect(theme.remote_theme.commits_behind).to eq(1)
end
it "can disable component" do
child = Fabricate(:theme, component: true)
put "/admin/themes/#{child.id}.json", params: { theme: { enabled: false } }
expect(response.status).to eq(200)
json = response.parsed_body
expect(json["theme"]["enabled"]).to eq(false)
expect(
UserHistory.where(
context: child.id.to_s,
action: UserHistory.actions[:disable_theme_component],
).size,
).to eq(1)
expect(json["theme"]["disabled_by"]["id"]).to eq(admin.id)
end
it "enabling/disabling a component creates the correct staff action log" do
child = Fabricate(:theme, component: true)
UserHistory.destroy_all
put "/admin/themes/#{child.id}.json", params: { theme: { enabled: false } }
expect(response.status).to eq(200)
expect(
UserHistory.where(
context: child.id.to_s,
action: UserHistory.actions[:disable_theme_component],
).size,
).to eq(1)
expect(
UserHistory.where(
context: child.id.to_s,
action: UserHistory.actions[:enable_theme_component],
).size,
).to eq(0)
put "/admin/themes/#{child.id}.json", params: { theme: { enabled: true } }
expect(response.status).to eq(200)
json = response.parsed_body
expect(
UserHistory.where(
context: child.id.to_s,
action: UserHistory.actions[:disable_theme_component],
).size,
).to eq(1)
expect(
UserHistory.where(
context: child.id.to_s,
action: UserHistory.actions[:enable_theme_component],
).size,
).to eq(1)
expect(json["theme"]["disabled_by"]).to eq(nil)
expect(json["theme"]["enabled"]).to eq(true)
end
it "handles import errors on update" do
theme.create_remote_theme!(remote_url: "https://example.com/repository")
theme.save!
# RemoteTheme is extensively tested, and setting up the test scaffold is a large overhead
# So use a stub here to test the controller
RemoteTheme
.any_instance
.stubs(:update_from_remote)
.raises(RemoteTheme::ImportError.new("error message"))
put "/admin/themes/#{theme.id}.json", params: { theme: { remote_update: true } }
expect(response.status).to eq(422)
expect(response.parsed_body["errors"].first).to eq("error message")
end
it "returns the right error message" do
theme.update!(component: true)
put "/admin/themes/#{theme.id}.json", params: { theme: { default: true } }
expect(response.status).to eq(400)
expect(response.parsed_body["errors"].first).to include(
I18n.t("themes.errors.component_no_default"),
)
end
it "prevents converting the default theme to a component" do
SiteSetting.default_theme_id = theme.id
put "/admin/themes/#{theme.id}.json", params: { theme: { component: true } }
# should this error message be localized? InvalidParameters :component
expect(response.status).to eq(400)
expect(response.parsed_body["errors"].first).to include("component")
end
end
shared_examples "theme update not allowed" do
it "prevents updates with a 404 response" do
SiteSetting.default_theme_id = -1
put "/admin/themes/#{theme.id}.json", params: { id: theme.id, theme: { default: true } }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
expect(SiteSetting.default_theme_id).not_to eq(theme.id)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme update not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme update not allowed"
end
end
describe "#destroy" do
let!(:theme) { Fabricate(:theme) }
context "when logged in as an admin" do
before { sign_in(admin) }
it "returns the right response when an invalid id is given" do
delete "/admin/themes/9999.json"
expect(response.status).to eq(400)
end
it "deletes the field's javascript cache" do
theme.set_field(
target: :common,
name: :header,
value: '<script>console.log("test")</script>',
)
theme.save!
javascript_cache =
theme
.theme_fields
.find_by(target_id: Theme.targets[:common], name: :header)
.javascript_cache
expect(javascript_cache).to_not eq(nil)
delete "/admin/themes/#{theme.id}.json"
expect(response.status).to eq(204)
expect { theme.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect { javascript_cache.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
shared_examples "theme deletion not allowed" do
it "prevent deletion with a 404 response" do
delete "/admin/themes/#{theme.id}.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
expect(theme.reload).to be_present
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme deletion not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme deletion not allowed"
end
end
describe "#preview" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "should return the right response when an invalid id is given" do
get "/admin/themes/9999/preview.json"
expect(response.status).to eq(400)
end
end
shared_examples "theme previews inaccessible" do
it "denies access with a 404 response" do
theme = Fabricate(:theme)
get "/admin/themes/#{theme.id}/preview.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme previews inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme previews inaccessible"
end
end
describe "#update_single_setting" do
let(:theme) { Fabricate(:theme) }
before do
theme.set_field(target: :settings, name: :yaml, value: "bg: red")
theme.save!
end
context "when logged in as an admin" do
before { sign_in(admin) }
it "should update a theme setting" do
put "/admin/themes/#{theme.id}/setting.json", params: { name: "bg", value: "green" }
expect(response.status).to eq(200)
expect(response.parsed_body["bg"]).to eq("green")
theme.reload
expect(theme.cached_settings[:bg]).to eq("green")
user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:change_theme_setting])
end
it "should clear a theme setting" do
put "/admin/themes/#{theme.id}/setting.json", params: { name: "bg" }
theme.reload
expect(response.status).to eq(200)
expect(theme.cached_settings[:bg]).to eq("")
end
end
shared_examples "theme update not allowed" do
it "prevents updates with a 404 response" do
put "/admin/themes/#{theme.id}/setting.json", params: { name: "bg", value: "green" }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
theme.reload
expect(theme.cached_settings[:bg]).to eq("red")
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "theme update not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "theme update not allowed"
end
end
end