Files
manyfold/spec/requests/model_files_spec.rb

360 lines
13 KiB
Ruby

require "rails_helper"
require "support/mock_directory"
# edit_model_model_files GET /models/:model_id/model_files/edit(.:format) model_files#bulk_edit
# model_model_files PATCH /models/:model_id/model_files/update(.:format) model_files#bulk_update
# POST /models/:model_id/model_files(.:format) model_files#create
# edit_model_model_file GET /models/:model_id/model_files/:id/edit(.:format) model_files#edit
# model_model_file GET /models/:model_id/model_files/:id(.:format) model_files#show
# PATCH /models/:model_id/model_files/:id(.:format) model_files#update
# PUT /models/:model_id/model_files/:id(.:format) model_files#update
# DELETE /models/:model_id/model_files/:id(.:format) model_files#destroy
RSpec.describe "Model Files" do
context "when signed out in multiuser mode", :after_first_run, :multiuser do
context "with a public model" do
let(:model) { create(:model, :public) }
let(:file) { create(:model_file, model: model) }
it "includes indexing directive header" do
allow(SiteSettings).to receive_messages(default_indexable: true, default_ai_indexable: false)
get "/models/#{model.to_param}/model_files/#{file.to_param}"
expect(response.headers["X-Robots-Tag"]).to eq "noai noimageai"
end
it "includes indexing directive meta tag" do
allow(SiteSettings).to receive_messages(default_indexable: true, default_ai_indexable: false)
get "/models/#{model.to_param}/model_files/#{file.to_param}"
expect(response.body).to include %(<meta name="robots" content="noai noimageai">)
end
end
end
[:multiuser, :singleuser].each do |mode|
context "when signed out in #{mode} mode", mode, :after_first_run do
context "when downloading via a signed ID" do
let!(:file) { create(:model_file, filename: "test.jpg") }
it "succeeds with a valid ID" do
id = file.signed_id(expires_in: 1.minute, purpose: "download")
get "/models/#{file.model.to_param}/model_files/signed/#{id}/#{file.filename}"
expect(response).to have_http_status(:success)
end
it "fails if expired" do
id = file.signed_id(expires_at: 1.minute.ago, purpose: "download")
get "/models/#{file.model.to_param}/model_files/signed/#{id}/#{file.filename}"
expect(response).to have_http_status(:not_found)
end
it "fails if purpose doesn't match" do
id = file.signed_id(expires_in: 1.minute, purpose: "shenanigans")
get "/models/#{file.model.to_param}/model_files/signed/#{id}/#{file.filename}"
expect(response).to have_http_status(:not_found)
end
it "fails if signed ID doesn't match URL id" do
another_file = create(:model_file, filename: "test2.jpg")
id = file.signed_id(expires_in: 1.minute, purpose: "download")
get "/models/#{file.model.to_param}/model_files/signed/#{id}/#{another_file.filename}"
expect(response).to have_http_status(:not_found)
end
end
end
end
context "when signed in" do
let!(:jpg_file) { create(:model_file, model: model, filename: "test.jpg") }
let!(:stl_file) { create(:model_file, model: model, filename: "test.stl") }
let!(:model) { create(:model, library: library, path: "model_one") }
let(:library) { create(:library, path: @library_path) } # rubocop:todo RSpec/InstanceVariable
around do |ex|
MockDirectory.create([
"model_one/test.stl",
"model_one/test.jpg"
]) do |path|
@library_path = path
ex.run
end
end
describe "GET /models/:model_id/model_files/edit", :as_moderator do
it "shows bulk update form" do
get bulk_edit_model_model_files_path(model)
expect(response).to have_http_status(:success)
end
end
describe "PATCH /models/:model_id/model_files/update", :as_moderator do
let(:params) { {model_files: {stl_file.public_id => "1", jpg_file.public_id => "0"}} }
it "bulk updates Y Up on the selected files" do
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(y_up: "1"))
}.to change { stl_file.reload.y_up }.from(false).to(true)
end
it "bulk updates presupported flag on the selected files" do
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(presupported: "1"))
}.to change { stl_file.reload.presupported }.from(false).to(true)
end
it "bulk updates printed flag on the selected files" do
get bulk_edit_model_model_files_path(model) # Do a get so we have controller reference available
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(printed: "1"))
}.to change { stl_file.listers(:printed).include? controller.current_user }.from(false).to(true)
end
it "renames selected files" do
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(pattern: "s", replacement: "n"))
}.to change { stl_file.reload.filename }.from("test.stl").to("tent.stl")
end
it "does not modify non-selected files" do
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(y_up: "1"))
}.not_to change { jpg_file.reload.y_up }
end
it "splits model" do
expect {
patch bulk_update_model_model_files_path(model, params: params.merge(split: "split"))
}.to change(Model, :count).by(1)
end
end
describe "GET /models/:model_id/model_files/:id/edit", :as_moderator do
it "shows edit page for file" do
get edit_model_model_file_path(model, stl_file)
expect(response).to have_http_status(:success)
end
end
describe "GET /models/:model_id/model_files/:id", :as_member do
it "renders file details page" do
get model_model_file_path(model, stl_file)
expect(response).to have_http_status :success
end
describe "GET a model file in its original file format" do
before do
get model_model_file_path(model, stl_file, format: :stl)
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "has correct MIME type" do
expect(response.media_type).to eq("model/stl")
end
it "does not set session cookies" do
expect(response.headers["set-cookie"]).to be_nil
end
end
describe "GET an image file in its original file format", :as_member do
before do
get model_model_file_path(model, jpg_file, format: :jpg)
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "has correct MIME type" do
expect(response.media_type).to eq("image/jpeg")
end
it "does not set session cookies" do
expect(response.headers["set-cookie"]).to be_nil
end
end
end
describe "POST /models/:model_id/model_files", :as_moderator do
context "when requesting a conversion" do
let(:params) { {convert: {id: stl_file.to_param, to: "threemf"}} }
it "queues a conversion job" do
expect { post model_model_files_path(model, params: params) }.to have_enqueued_job(Analysis::FileConversionJob).with(stl_file.id, :threemf)
end
it "redirects back to file list" do
post model_model_files_path(model, params: params)
expect(response).to redirect_to model_model_file_path(model, stl_file)
end
it "shows success message if conversion job was queued" do
post model_model_files_path(model, params: params)
follow_redirect!
expect(response.body).to include "alert-info"
end
end
context "when uploading a file" do
let(:params) {
{
model: {
file: {
"0" => {
id: "upload_key",
name: "test.stl"
}
}
}
}
}
it "queues post-upload job" do # rubocop:disable RSpec/ExampleLength
expect { post model_model_files_path(model, params: params) }
.to have_enqueued_job(ProcessUploadedFileJob)
.with(Library.first.id, {
id: "upload_key",
storage: "cache",
metadata: {
filename: "test.stl"
}
}, model: model).once
end
it "rate limits file uploads" do
Rails.cache.increment("rate-limit:model_files:127.0.0.1", 10, expires_in: 1.minute)
post model_model_files_path(model, params: params)
expect(response).to have_http_status :too_many_requests
end
end
context "when uploading a file with attempted path traversal" do
let(:params) {
{
model: {
file: {
"0" => {
id: "upload_key",
name: "../test.stl"
}
}
}
}
}
it "queues post-upload job with sanitized path" do # rubocop:disable RSpec/ExampleLength
expect { post model_model_files_path(model, params: params) }
.to have_enqueued_job(ProcessUploadedFileJob)
.with(Library.first.id, {
id: "upload_key",
storage: "cache",
metadata: {
filename: "test.stl"
}
}, model: model).once
end
end
it "shows an error with missing parameters" do
post model_model_files_path(model, params: {})
expect(response).to have_http_status(:unprocessable_content)
end
end
describe "PATCH /models/:model_id/model_files/:id", :as_moderator do
it "updates the file" do
patch model_model_file_path(model, stl_file), params: {model_file: {name: "name"}}
expect(response).to redirect_to(model_model_file_path(model, stl_file))
end
end
describe "DELETE /models/:model_id/model_files/:id", :as_moderator do
it "removes the file" do
delete model_model_file_path(model, stl_file)
expect(response).to redirect_to(model_path(model))
end
end
describe "GET /models/:model_id/model_files/:filename", :as_member do
describe "GET a model file from its filename" do
before do
create(:model_file, model: model, filename: "subfolder/test.stl")
get model_model_file_by_filename_path(model_id: model.to_param, id: "subfolder/test", format: :stl)
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "has correct MIME type" do
expect(response.media_type).to eq("model/stl")
end
end
describe "GET a model file from its filename with uppercase content" do
before do
file = create(:model_file, model: model, filename: "subfolder/Test.STL")
get model_model_file_by_filename_path(model_id: model.to_param, id: file.filename)
end
it "returns http success" do
expect(response).to have_http_status(:success)
end
it "has correct MIME type" do
expect(response.media_type).to eq("model/stl")
end
end
end
end
context "without permission on a model", :as_contributor do
let(:forbidden_model) { create(:model) }
let(:forbidden_file) { create(:model_file, model: forbidden_model) }
before do
allow(SiteSettings).to receive(:default_viewer_role).and_return(:private)
end
it "cannot view the file" do
get model_model_file_path(forbidden_model, forbidden_file)
expect(response).to have_http_status :forbidden
end
it "cannot edit the file" do
get edit_model_model_file_path(forbidden_model, forbidden_file)
expect(response).to have_http_status :forbidden
end
it "cannot update the file" do
put model_model_file_path(forbidden_model, forbidden_file, params: {model_file: {printed: "1"}})
expect(response).to have_http_status :forbidden
end
it "cannot convert file to different format" do # rubocop:disable RSpec/MultipleExpectations
params = {convert: {id: forbidden_file.to_param, to: "threemf"}}
post model_model_files_path(forbidden_model, params: params)
expect(Analysis::FileConversionJob).not_to have_been_enqueued
expect(response).to have_http_status :forbidden
end
it "cannot delete the file" do
delete model_model_file_path(forbidden_model, forbidden_file)
expect(response).to have_http_status :forbidden
end
it "cannot bulk edit files" do
get bulk_edit_model_model_files_path(forbidden_model)
expect(response).to have_http_status :forbidden
end
it "cannot bulk update files" do
params = {model_files: {forbidden_file.public_id => "1", :printed => "1"}}
patch bulk_update_model_model_files_path(forbidden_model, params: params)
expect(response).to have_http_status :forbidden
end
end
end