From 3dd9d7fbce366862a7505c63bf461e9fa7c426c1 Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:55:42 +0000 Subject: [PATCH 1/5] Make work and chapter use delete_all for comments and kudos --- app/models/chapter.rb | 13 +- app/models/work.rb | 3 +- lib/responder.rb | 3 +- .../works/default_rails_actions_spec.rb | 1738 +++++++++-------- 4 files changed, 897 insertions(+), 860 deletions(-) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 893dade0ee7..ed1e6ed4f53 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -5,11 +5,13 @@ class Chapter < ApplicationRecord include WorkChapterCountCaching include CreationNotifier include Creatable + include Responder belongs_to :work, inverse_of: :chapters # acts_as_list scope: 'work_id = #{work_id}' acts_as_commentable + has_many :comments, as: :commentable validates_length_of :title, allow_blank: true, maximum: ArchiveConfig.TITLE_MAX, too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) @@ -79,7 +81,9 @@ def fix_positions after_save :invalidate_chapter_count, if: Proc.new { |chapter| chapter.saved_change_to_posted? } - before_destroy :fix_positions_before_destroy, :invalidate_chapter_count + before_destroy :fix_positions_before_destroy, :invalidate_chapter_count, :delete_all_comments + after_destroy :update_work_stats + def fix_positions_before_destroy if work&.persisted? && position chapters = work.chapters.where(["position > ?", position]) @@ -87,6 +91,13 @@ def fix_positions_before_destroy end end + def delete_all_comments + inbox_comments = InboxComment.where(feedback_comment_id: total_comments.pluck(:id)) + + total_comments.in_batches.delete_all + inbox_comments.in_batches.delete_all + end + after_commit :update_series_index def update_series_index return unless work&.series.present? && should_reindex_series? diff --git a/app/models/work.rb b/app/models/work.rb index 78919a2ed50..99ee57774a8 100755 --- a/app/models/work.rb +++ b/app/models/work.rb @@ -41,8 +41,9 @@ class Work < ApplicationRecord accepts_nested_attributes_for :challenge_claims acts_as_commentable + has_many :comments, as: :commentable has_many :total_comments, class_name: 'Comment', through: :chapters - has_many :kudos, as: :commentable, dependent: :destroy + has_many :kudos, as: :commentable, dependent: :delete_all has_many :original_creators, class_name: "WorkOriginalCreator", dependent: :destroy diff --git a/lib/responder.rb b/lib/responder.rb index 4c31ec63ff9..67563df30bf 100644 --- a/lib/responder.rb +++ b/lib/responder.rb @@ -13,9 +13,10 @@ def get_work work = self.commentable elsif self.respond_to?(:bookmarkable) work = self.bookmarkable + elsif self.respond_to?(:work) + work = self.work end work.is_a?(Work) ? work : nil end end - diff --git a/spec/controllers/works/default_rails_actions_spec.rb b/spec/controllers/works/default_rails_actions_spec.rb index 14301cde983..981108c216f 100644 --- a/spec/controllers/works/default_rails_actions_spec.rb +++ b/spec/controllers/works/default_rails_actions_spec.rb @@ -20,862 +20,862 @@ suspended_user.update!(suspended: true, suspended_until: 1.week.from_now) work end - - describe "before_action #clean_work_search_params" do - let(:params) { {} } - def call_with_params(params) - controller.params = { work_search: params } - controller.params[:work_search] = controller.clean_work_search_params - end - - context "when no work search parameters are given" do - it "redirects to the login screen when no user is logged in" do - get :clean_work_search_params, params: params - it_redirects_to_with_error(new_user_session_path, - "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") - end - end - - context "when the query contains countable search parameters" do - it "escapes less and greater than in query" do - [ - { params: "< 5 words", expected: "< 5 words", message: "Should escape <" }, - { params: "> 5 words", expected: "> 5 words", message: "Should escape >" }, - ].each do |settings| - call_with_params(query: settings[:params]) - expect(controller.params[:work_search][:query]) - .to eq(settings[:expected]), settings[:message] - end - end - - it "converts 'word' to 'word_count'" do - call_with_params(query: "word:6") - expect(controller.params[:work_search][:word_count]).to eq("6") - end - - it "converts 'words' to 'word_count'" do - call_with_params(query: "words:7") - expect(controller.params[:work_search][:word_count]).to eq("7") - end - - it "converts 'hits' queries to 'hits'" do - call_with_params(query: "hits:8") - expect(controller.params[:work_search][:hits]).to eq("8") - end - - it "converts other queries to (pluralized term)_count" do - %w(kudo comment bookmark).each do |term| - call_with_params(query: "#{term}:9") - expect(controller.params[:work_search]["#{term.pluralize}_count"]) - .to eq("9"), "Search term '#{term}' should become #{term.pluralize}_count key" - end - end - end - - context "when sort parameters are provided" do - it "converts variations on 'sorted by: X' into :sort_column key" do - [ - "sort by: words", - "sorted by: words", - "sorted: words", - "sort: words", - "sort by < words", - "sort by > words", - "sort by = words" - ].each do |query| - call_with_params(query: query) - expect(controller.params[:work_search][:sort_column]) - .to eq("word_count"), "Sort command '#{query}' should be converted to :sort_column" - end - end - - it "converts variations on sort columns to column name" do - [ - { query: "sort by: word count", expected: "word_count" }, - { query: "sort by: words", expected: "word_count" }, - { query: "sort by: word", expected: "word_count" }, - { query: "sort by: creator", expected: "authors_to_sort_on" }, - { query: "sort by: title", expected: "title_to_sort_on" }, - { query: "sort by: date", expected: "created_at" }, - { query: "sort by: date posted", expected: "created_at" }, - { query: "sort by: hits", expected: "hits" }, - { query: "sort by: kudos", expected: "kudos_count" }, - { query: "sort by: comments", expected: "comments_count" }, - { query: "sort by: bookmarks", expected: "bookmarks_count" }, - ].each do |settings| - call_with_params(query: settings[:query]) - actual = controller.params[:work_search][:sort_column] - expect(actual) - .to eq(settings[:expected]), - "Query '#{settings[:query]}' should be converted to :sort_column '#{settings[:expected]}' but is '#{actual}'" - end - end - - it "converts 'ascending' or '>' into :sort_direction key 'asc'" do - [ - "sort > word_count", - "sort: word_count ascending", - "sort: hits ascending", - ].each do |query| - call_with_params(query: query) - expect(controller.params[:work_search][:sort_direction]).to eq("asc") - end - end - - it "converts 'descending' or '<' into :sort_direction key 'desc'" do - [ - "sort < word_count", - "sort: word_count descending", - "sort: hits descending", - ].each do |query| - call_with_params(query: query) - expect(controller.params[:work_search][:sort_direction]).to eq("desc") - end - end - - # The rest of these are probably bugs - it "returns no sort column if there is NO punctuation after 'sort by' clause" do - call_with_params(query: "sort by word count") - expect(controller.params[:work_search][:sort_column]).to be_nil - end - - it "can't search by date updated" do - [ - { query: "sort by: date updated", expected: "revised_at" }, - ].each do |settings| - call_with_params(query: settings[:query]) - expect(controller.params[:work_search][:sort_column]).to eq("created_at") # should be revised_at - end - end - - it "can't sort ascending if more than one word follows the colon" do - [ - "sort by: word count ascending", - ].each do |query| - call_with_params(query: query) - expect(controller.params[:work_search][:sort_direction]).to be_nil - end - end - end - - context "when the query contains categories" do - it "surrounds categories in quotes" do - [ - { query: "M/F sort by: comments", expected: "\"m/f\"" }, - { query: "f/f Scully/Reyes", expected: "\"f/f\" Scully/Reyes" }, - ].each do |settings| - call_with_params(query: settings[:query]) - expect(controller.params[:work_search][:query]).to eq(settings[:expected]) - end - end - - it "does not surround categories in quotes when it shouldn't" do - query = "sam/frodo sort by: word" - call_with_params(query: query) - expect(controller.params[:work_search][:query]).to eq("sam/frodo") - end - end - end - - describe "new" do - it "doesn't return the form for anyone not logged in" do - get :new - it_redirects_to_with_error(new_user_session_path, - "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") - end - - it "renders the form if logged in" do - fake_login - get :new - expect(response).to render_template("new") - end - - it "errors and redirects to user page when user is banned" do - fake_login_known_user(banned_user) - get :new - it_redirects_to_simple(user_path(banned_user)) - expect(flash[:error]).to include("Your account has been banned.") - end - end - - describe "create" do - let(:user) { create(:user) } - - before { fake_login_known_user(user) } - - it "doesn't allow a user to create a work in a series that they don't own" do - @series = create(:series) - work_attributes = attributes_for(:work).except(:posted) - work_attributes[:series_attributes] = { id: @series.id } - expect { - post :create, params: { work: work_attributes } - }.not_to change { @series.works.all.count } - expect(response).to render_template :new - expect(assigns[:work].errors.full_messages).to \ - include("You can't add a work to that series.") - end - - it "doesn't allow a user to submit only a pseud that is not theirs" do - user2 = create(:user) - work_attributes = attributes_for(:work).except(:posted) - work_attributes[:author_attributes] = { ids: [user2.pseuds.first.id] } - expect { - post :create, params: { work: work_attributes } - }.to_not change(Work, :count) - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include "You're not allowed to use that pseud." - end - - it "renders new if the work has invalid pseuds" do - work_attributes = attributes_for(:work).except(:posted) - work_attributes[:author_attributes] = { ids: user.pseud_ids, - byline: "*impossible*" } - post :create, params: { work: work_attributes } - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include "Invalid creator: Could not find a pseud *impossible*." - end - - it "renders new if the work has ambiguous pseuds" do - create(:pseud, name: "ambiguous") - create(:pseud, name: "ambiguous") - work_attributes = attributes_for(:work).except(:posted) - work_attributes[:author_attributes] = { ids: user.pseud_ids, - byline: "ambiguous" } - post :create, params: { work: work_attributes } - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include "Invalid creator: The pseud ambiguous is ambiguous." - end - - it "renders new if the work has noncanonical warnings" do - work_attributes = attributes_for(:work).except(:posted, :archive_warning_string).merge(archive_warning_string: "Warning") - post :create, params: { work: work_attributes } - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include /Only canonical warning tags are allowed./ - end - - it "renders new if the work has noncanonical rating" do - work_attributes = attributes_for(:work).except(:posted, :rating_string).merge(rating_string: "Rating") - post :create, params: { work: work_attributes } - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include /Only canonical rating tags are allowed./ - end - - it "renders new if the work has noncanonical category" do - work_attributes = attributes_for(:work).except(:posted).merge(category_strings: ["Category"]) - post :create, params: { work: work_attributes } - expect(response).to render_template("new") - expect(assigns[:work].errors.full_messages).to \ - include /Only canonical category tags are allowed./ - end - - context "as a tag wrangler" do - let(:user) { create(:tag_wrangler) } - - it "does not set wrangling activity when posting with a new fandom" do - work_attributes = attributes_for(:work).except(:posted, :fandom_string).merge(fandom_string: "New Fandom") - post :create, params: { work: work_attributes } - expect(user.last_wrangling_activity).to be_nil - end - - it "does not set wrangling activity when posting with an unsorted tag" do - tag = create(:unsorted_tag) - work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) - post :create, params: { work: work_attributes } - expect(user.last_wrangling_activity).to be_nil - end - end - - it "errors and redirects to user page when user is banned" do - fake_login_known_user(banned_user) - tag = create(:unsorted_tag) - work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) - post :create, params: { work: work_attributes } - it_redirects_to_simple(user_path(banned_user)) - expect(flash[:error]).to include("Your account has been banned.") - end - end - - describe "show" do - let(:work) { create(:work) } - - it "doesn't error when a work has no fandoms" do - work_no_fandoms = build(:work, fandom_string: "") - work_no_fandoms.save!(validate: false) - fake_login - - get :show, params: { id: work_no_fandoms.id } - - expect(assigns(:page_title)).to include "No fandom specified" - end - - it "assigns @page_subtitle with unrevealed work and not @page_title" do - work.update!(in_unrevealed_collection: true) - get :show, params: { id: work.id } - expect(assigns[:page_subtitle]).to eq("Mystery Work") - expect(assigns[:page_title]).to be_nil - end - - context "when work does not exist" do - it "raises an error" do - expect do - get :show, params: { id: "999999999" } - end.to raise_error ActiveRecord::RecordNotFound - end - end - end - - describe "share" do - it "returns a 404 response for unrevealed works" do - unrevealed_collection = create :unrevealed_collection - unrevealed_work = create :work, collections: [unrevealed_collection] - - get :share, params: { id: unrevealed_work.id }, xhr: true - expect(response.status).to eq(404) - end - - it "redirects to referer with an error for non-ajax warnings requests" do - work = create(:work) - referer = work_path(work) - request.headers["HTTP_REFERER"] = referer - get :share, params: { id: work.id } - it_redirects_to_with_error(referer, "Sorry, you need to have JavaScript enabled for this.") - end - end - - describe "index" do - before do - @fandom = create(:canonical_fandom) - @work = create(:work, fandom_string: @fandom.name) - end - - it "returns the work" do - get :index - expect(assigns(:works)).to include(@work) - end - - it "sets the fandom when given a fandom id" do - params = { fandom_id: @fandom.id } - get :index, params: params - expect(assigns(:fandom)).to eq(@fandom) - end - - describe "when the fandom id is invalid" do - it "raises a 404 for an invalid id" do - params = { fandom_id: 0 } - expect { get :index, params: params } - .to raise_error ActiveRecord::RecordNotFound - end - end - - describe "when the fandom id is empty" do - it "returns the work" do - params = { fandom_id: nil } - get :index, params: params - expect(assigns(:works)).to include(@work) - end - end - - describe "without caching" do - before do - AdminSetting.first.update_attribute(:enable_test_caching, false) - end - - it "returns the result with different works the second time" do - get :index - expect(assigns(:works)).to include(@work) - work2 = create(:work) - get :index - expect(assigns(:works)).to include(work2) - end - end - - describe "with caching" do - before do - AdminSetting.first.update_attribute(:enable_test_caching, true) - end - - context "with NO owner tag" do - it "returns the same result the second time when a new work is created within the expiration time" do - get :index - expect(assigns(:works)).to include(@work) - work2 = create(:work) - run_all_indexing_jobs - get :index - expect(assigns(:works)).not_to include(work2) - end - end - - context "with a valid owner tag" do - before do - @fandom2 = create(:canonical_fandom) - @work2 = create(:work, fandom_string: @fandom2.name) - run_all_indexing_jobs - end - - it "only gets works under that tag" do - get :index, params: { tag_id: @fandom.name } - expect(assigns(:works).items).to include(@work) - expect(assigns(:works).items).not_to include(@work2) - end - - it "shows different results on second page" do - get :index, params: { tag_id: @fandom.name, page: 2 } - expect(assigns(:works).items).not_to include(@work) - end - - context "with restricted works" do - before do - @work2 = create(:work, fandom_string: @fandom.name, restricted: true) - run_all_indexing_jobs - end - - it "shows restricted works to guests" do - get :index, params: { tag_id: @fandom.name } - expect(assigns(:works).items).to include(@work) - expect(assigns(:works).items).not_to include(@work2) - end - - end - - context "when tag is a synonym" do - let(:fandom_synonym) { create(:fandom, merger: @fandom) } - - it "redirects to the merger's work index" do - params = { tag_id: fandom_synonym.name } - get :index, params: params - it_redirects_to tag_works_path(@fandom) - end - - context "when collection is specified" do - let(:collection) { create(:collection) } - - it "redirects to the merger's collection works index" do - params = { tag_id: fandom_synonym.name, collection_id: collection.name } - get :index, params: params - it_redirects_to collection_tag_works_path(collection, @fandom) - end - end - end - end - end - - context "with an invalid owner tag" do - it "raises an error" do - params = { tag_id: "nonexistent_tag" } - expect { get :index, params: params }.to raise_error( - ActiveRecord::RecordNotFound, - "Couldn't find tag named 'nonexistent_tag'" - ) - end - end - - context "with an invalid owner user" do - it "raises an error" do - params = { user_id: "nonexistent_user" } - expect { get :index, params: params }.to raise_error( - ActiveRecord::RecordNotFound - ) - end - - context "with an invalid pseud" do - it "raises an error" do - params = { user_id: "nonexistent_user", pseud_id: "nonexistent_pseud" } - expect { get :index, params: params }.to raise_error( - ActiveRecord::RecordNotFound - ) - end - end - end - - context "with a valid owner user" do - let(:user) { create(:user) } - let!(:user_work) { create(:work, authors: [user.default_pseud]) } - let(:pseud) { create(:pseud, user: user) } - let!(:pseud_work) { create(:work, authors: [pseud]) } - - before { run_all_indexing_jobs } - - it "includes only works for that user" do - params = { user_id: user.login } - get :index, params: params - expect(assigns(:works).items).to include(user_work, pseud_work) - expect(assigns(:works).items).not_to include(@work) - end - - context "with a valid pseud" do - it "includes only works for that pseud" do - params = { user_id: user.login, pseud_id: pseud.name } - get :index, params: params - expect(assigns(:works).items).to include(pseud_work) - expect(assigns(:works).items).not_to include(user_work, @work) - end - end - - context "with an invalid pseud" do - it "includes all of that user's works" do - params = { user_id: user.login, pseud_id: "nonexistent_pseud" } - get :index, params: params - expect(assigns(:works).items).to include(user_work, pseud_work) - expect(assigns(:works).items).not_to include(@work) - end - end - end - end - - describe "update" do - let(:update_user) { create(:user) } - let(:update_work) { - work = create(:work, authors: [update_user.default_pseud]) - create(:chapter, work: work) - work - } - - before do - fake_login_known_user(update_user) - end - - it "doesn't allow the user to add a series that they don't own" do - @series = create(:series) - attrs = { series_attributes: { id: @series.id } } - expect { - put :update, params: { id: update_work.id, work: attrs } - }.not_to change { @series.works.all.count } - expect(response).to render_template :edit - expect(assigns[:work].errors.full_messages).to \ - include("You can't add a work to that series.") - end - - it "redirects to the edit page if the work could not be saved" do - allow_any_instance_of(Work).to receive(:save).and_return(false) - update_work.fandom_string = "Testing" - attrs = { title: "New Work Title" } - put :update, params: { id: update_work.id, work: attrs } - expect(response).to render_template :edit - allow_any_instance_of(Work).to receive(:save).and_call_original - end - - it "updates the editor's pseuds for all chapters" do - new_pseud = create(:pseud, user: update_user) - put :update, params: { id: update_work.id, work: { author_attributes: { ids: [new_pseud.id] } } } - expect(update_work.pseuds.reload).to contain_exactly(new_pseud) - update_work.chapters.reload.each do |c| - expect(c.pseuds.reload).to contain_exactly(new_pseud) - end - end - - it "allows the user to invite co-creators" do - co_creator = create(:user) - co_creator.preference.update!(allow_cocreator: true) - put :update, params: { id: update_work.id, work: { author_attributes: { byline: co_creator.login } } } - expect(update_work.pseuds.reload).not_to include(co_creator.default_pseud) - expect(update_work.user_has_creator_invite?(co_creator)).to be_truthy - end - - it "prevents inviting users who have disallowed co-creators" do - no_co_creator = create(:user) - no_co_creator.preference.update!(allow_cocreator: false) - put :update, params: { id: update_work.id, work: { author_attributes: { byline: no_co_creator.login } } } - expect(response).to render_template :edit - expect(assigns[:work].errors.full_messages).to \ - include "Invalid creator: #{no_co_creator.login} does not allow others to invite them to be a co-creator." - expect(update_work.pseuds.reload).not_to include(no_co_creator.default_pseud) - expect(update_work.user_has_creator_invite?(no_co_creator)).to be_falsey - end - - context "when the work has broken dates" do - let(:update_work) { create(:work, authors: [update_user.default_pseud]) } - let(:update_chapter) { update_work.first_chapter } - - let(:attributes) do - { - backdate: "1", - chapter_attributes: { - published_at: "2021-09-01" - } - } - end - - before do - # Work where chapter published_at did not override revised_at, times - # taken from AO3-5392 - update_work.update_column(:revised_at, Time.new(2018, 4, 22, 23, 51, 42, "+04:00")) - update_chapter.update_column(:published_at, Date.new(2015, 7, 23)) - end - - it "can be backdated" do - put :update, params: { id: update_work.id, work: attributes } - - expect(update_chapter.reload.published_at).to eq(Date.new(2021, 9, 1)) - expect(update_work.reload.revised_at).to eq(Time.utc(2021, 9, 1, 12)) # noon UTC - end - end - - # If the time zone in config/application.rb is changed to something other - # than the default (UTC), these tests will need adjusting: - context "when redating to the present" do - let!(:update_work) do - # November 30, 2 PM UTC -- no time zone oddities here - travel_to(Time.utc(2021, 11, 30, 14)) do - create(:work, authors: [update_user.default_pseud]) - end - end - - let(:attributes) do - { - backdate: "1", - chapter_attributes: { - published_at: "2021-12-05" - } - } - end - - before do - travel_to(redate_time) - - # Simulate the system time being UTC: - allow(Time).to receive(:now).and_return(redate_time) - allow(DateTime).to receive(:now).and_return(redate_time) - allow(Date).to receive(:today).and_return(redate_time.to_date) - - put :update, params: { id: update_work.id, work: attributes } - end - - context "before midnight UTC and after midnight Samara" do - # December 5, 3 AM Europe/Samara (UTC+04:00) -- still December 4 in UTC - let(:redate_time) { Time.new(2021, 12, 5, 3, 0, 0, "+04:00") } - - it "prevents setting the publication date to the future" do - expect(response).to render_template :edit - expect(assigns[:work].errors.full_messages).to \ - include("Publication date can't be in the future.") - end - end - - context "before noon UTC" do - # December 5, 6 AM Europe/Samara -- before noon, but after midnight in both time zones - let(:redate_time) { Time.new(2021, 12, 5, 6, 0, 0, "+04:00") } - - it "doesn't set revised_at to the future" do - update_work.reload - expect(update_work.revised_at).to be <= Time.current - end - end - end - - it "errors and redirects to user page when user is banned" do - fake_login_known_user(banned_user) - attrs = { title: "New Work Title" } - put :update, params: { id: banned_users_work.id, work: attrs } - it_redirects_to_simple(user_path(banned_user)) - expect(flash[:error]).to include("Your account has been banned.") - end - end - - describe "collected" do - let(:collection) { create(:collection) } - let(:collected_user) { create(:user) } - - it "returns not found error if user does not exist" do - expect do - get :collected, params: { user_id: "dummyuser" } - end.to raise_error(ActiveRecord::RecordNotFound) - end - - it "returns not found error if no user is set" do - expect do - get :collected - end.to raise_error(ActiveRecord::RecordNotFound) - end - - context "with anonymous works" do - let(:anonymous_collection) { create(:anonymous_collection) } - - let!(:work) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: collection.name) - end - - let!(:anonymous_work) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: anonymous_collection.name) - end - - before { run_all_indexing_jobs } - - it "does not return anonymous works in collections for guests" do - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work) - expect(assigns(:works)).not_to include(anonymous_work) - end - - it "does not return anonymous works in collections for logged-in users" do - fake_login - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work) - expect(assigns(:works)).not_to include(anonymous_work) - end - - it "returns anonymous works in collections for the author" do - fake_login_known_user(collected_user) - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work, anonymous_work) - end - end - - context "with restricted works" do - let(:collected_fandom) { create(:canonical_fandom) } - let(:collected_fandom_2) { create(:canonical_fandom) } - - let!(:unrestricted_work) do - create(:work, - authors: [collected_user.default_pseud], - fandom_string: collected_fandom.name) - end - - let!(:unrestricted_work_in_collection) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: collection.name, - fandom_string: collected_fandom.name) - end - - let!(:unrestricted_work_2_in_collection) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: collection.name, - fandom_string: collected_fandom_2.name) - end - - let!(:restricted_work_in_collection) do - create(:work, - restricted: true, - authors: [collected_user.default_pseud], - collection_names: collection.name, - fandom_string: collected_fandom.name) - end - - before { run_all_indexing_jobs } - - context "as a guest" do - it "renders the collected form" do - get :collected, params: { user_id: collected_user.login } - expect(response).to render_template("collected") - end - - it "returns ONLY unrestricted works in collections" do - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(unrestricted_work_in_collection, unrestricted_work_2_in_collection) - expect(assigns(:works)).not_to include(unrestricted_work, restricted_work_in_collection) - end - - it "returns filtered works when search parameters are provided" do - get :collected, params: { user_id: collected_user.login, work_search: { query: "fandom_ids:#{collected_fandom_2.id}" }} - expect(assigns(:works)).to include(unrestricted_work_2_in_collection) - expect(assigns(:works)).not_to include(unrestricted_work_in_collection) - end - end - - context "with a logged-in user" do - before { fake_login } - - it "returns ONLY works in collections" do - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(unrestricted_work_in_collection, restricted_work_in_collection) - expect(assigns(:works)).not_to include(unrestricted_work) - end - end - end - - context "with unrevealed works" do - let(:unrevealed_collection) { create(:unrevealed_collection) } - - let!(:work) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: collection.name) - end - - let!(:unrevealed_work) do - create(:work, - authors: [collected_user.default_pseud], - collection_names: unrevealed_collection.name) - end - - before { run_all_indexing_jobs } - - it "doesn't return unrevealed works in collections for guests" do - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work) - expect(assigns(:works)).not_to include(unrevealed_work) - end - - it "doesn't return unrevealed works in collections for logged-in users" do - fake_login - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work) - expect(assigns(:works)).not_to include(unrevealed_work) - end - - it "returns unrevealed works in collections for the author" do - fake_login_known_user(collected_user) - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works)).to include(work, unrevealed_work) - end - end - - context "with sorting options" do - let!(:new_work) do - create(:work, - title: "New Title", - authors: [collected_user.default_pseud], - collection_names: collection.name, - created_at: 3.days.ago, - revised_at: 3.days.ago) - end - - let!(:old_work) do - create(:work, - title: "Old Title", - authors: [collected_user.default_pseud], - collection_names: collection.name, - created_at: 30.days.ago, - revised_at: 30.days.ago) - end - - let!(:revised_work) do - create(:work, - title: "Revised Title", - authors: [collected_user.default_pseud], - collection_names: collection.name, - created_at: 20.days.ago, - revised_at: 2.days.ago) - end - - before { run_all_indexing_jobs } - - it "sorts by date" do - get :collected, params: { user_id: collected_user.login } - expect(assigns(:works).map(&:title)).to eq([revised_work, new_work, old_work].map(&:title)) - get :collected, params: { user_id: collected_user.login, work_search: { sort_direction: "asc" } } - expect(assigns(:works).map(&:title)).to eq([old_work, new_work, revised_work].map(&:title)) - end - - it "sorts by title" do - get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on" } } - expect(assigns(:works).map(&:title)).to eq([new_work, old_work, revised_work].map(&:title)) - get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on", sort_direction: "desc" } } - expect(assigns(:works).map(&:title)).to eq([revised_work, old_work, new_work].map(&:title)) - end - end - end + # describe "before_action #clean_work_search_params" do + # let(:params) { {} } + + # def call_with_params(params) + # controller.params = { work_search: params } + # controller.params[:work_search] = controller.clean_work_search_params + # end + + # context "when no work search parameters are given" do + # it "redirects to the login screen when no user is logged in" do + # get :clean_work_search_params, params: params + # it_redirects_to_with_error(new_user_session_path, + # "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") + # end + # end + + # context "when the query contains countable search parameters" do + # it "escapes less and greater than in query" do + # [ + # { params: "< 5 words", expected: "< 5 words", message: "Should escape <" }, + # { params: "> 5 words", expected: "> 5 words", message: "Should escape >" }, + # ].each do |settings| + # call_with_params(query: settings[:params]) + # expect(controller.params[:work_search][:query]) + # .to eq(settings[:expected]), settings[:message] + # end + # end + + # it "converts 'word' to 'word_count'" do + # call_with_params(query: "word:6") + # expect(controller.params[:work_search][:word_count]).to eq("6") + # end + + # it "converts 'words' to 'word_count'" do + # call_with_params(query: "words:7") + # expect(controller.params[:work_search][:word_count]).to eq("7") + # end + + # it "converts 'hits' queries to 'hits'" do + # call_with_params(query: "hits:8") + # expect(controller.params[:work_search][:hits]).to eq("8") + # end + + # it "converts other queries to (pluralized term)_count" do + # %w(kudo comment bookmark).each do |term| + # call_with_params(query: "#{term}:9") + # expect(controller.params[:work_search]["#{term.pluralize}_count"]) + # .to eq("9"), "Search term '#{term}' should become #{term.pluralize}_count key" + # end + # end + # end + + # context "when sort parameters are provided" do + # it "converts variations on 'sorted by: X' into :sort_column key" do + # [ + # "sort by: words", + # "sorted by: words", + # "sorted: words", + # "sort: words", + # "sort by < words", + # "sort by > words", + # "sort by = words" + # ].each do |query| + # call_with_params(query: query) + # expect(controller.params[:work_search][:sort_column]) + # .to eq("word_count"), "Sort command '#{query}' should be converted to :sort_column" + # end + # end + + # it "converts variations on sort columns to column name" do + # [ + # { query: "sort by: word count", expected: "word_count" }, + # { query: "sort by: words", expected: "word_count" }, + # { query: "sort by: word", expected: "word_count" }, + # { query: "sort by: creator", expected: "authors_to_sort_on" }, + # { query: "sort by: title", expected: "title_to_sort_on" }, + # { query: "sort by: date", expected: "created_at" }, + # { query: "sort by: date posted", expected: "created_at" }, + # { query: "sort by: hits", expected: "hits" }, + # { query: "sort by: kudos", expected: "kudos_count" }, + # { query: "sort by: comments", expected: "comments_count" }, + # { query: "sort by: bookmarks", expected: "bookmarks_count" }, + # ].each do |settings| + # call_with_params(query: settings[:query]) + # actual = controller.params[:work_search][:sort_column] + # expect(actual) + # .to eq(settings[:expected]), + # "Query '#{settings[:query]}' should be converted to :sort_column '#{settings[:expected]}' but is '#{actual}'" + # end + # end + + # it "converts 'ascending' or '>' into :sort_direction key 'asc'" do + # [ + # "sort > word_count", + # "sort: word_count ascending", + # "sort: hits ascending", + # ].each do |query| + # call_with_params(query: query) + # expect(controller.params[:work_search][:sort_direction]).to eq("asc") + # end + # end + + # it "converts 'descending' or '<' into :sort_direction key 'desc'" do + # [ + # "sort < word_count", + # "sort: word_count descending", + # "sort: hits descending", + # ].each do |query| + # call_with_params(query: query) + # expect(controller.params[:work_search][:sort_direction]).to eq("desc") + # end + # end + + # # The rest of these are probably bugs + # it "returns no sort column if there is NO punctuation after 'sort by' clause" do + # call_with_params(query: "sort by word count") + # expect(controller.params[:work_search][:sort_column]).to be_nil + # end + + # it "can't search by date updated" do + # [ + # { query: "sort by: date updated", expected: "revised_at" }, + # ].each do |settings| + # call_with_params(query: settings[:query]) + # expect(controller.params[:work_search][:sort_column]).to eq("created_at") # should be revised_at + # end + # end + + # it "can't sort ascending if more than one word follows the colon" do + # [ + # "sort by: word count ascending", + # ].each do |query| + # call_with_params(query: query) + # expect(controller.params[:work_search][:sort_direction]).to be_nil + # end + # end + # end + + # context "when the query contains categories" do + # it "surrounds categories in quotes" do + # [ + # { query: "M/F sort by: comments", expected: "\"m/f\"" }, + # { query: "f/f Scully/Reyes", expected: "\"f/f\" Scully/Reyes" }, + # ].each do |settings| + # call_with_params(query: settings[:query]) + # expect(controller.params[:work_search][:query]).to eq(settings[:expected]) + # end + # end + + # it "does not surround categories in quotes when it shouldn't" do + # query = "sam/frodo sort by: word" + # call_with_params(query: query) + # expect(controller.params[:work_search][:query]).to eq("sam/frodo") + # end + # end + # end + + # describe "new" do + # it "doesn't return the form for anyone not logged in" do + # get :new + # it_redirects_to_with_error(new_user_session_path, + # "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") + # end + + # it "renders the form if logged in" do + # fake_login + # get :new + # expect(response).to render_template("new") + # end + + # it "errors and redirects to user page when user is banned" do + # fake_login_known_user(banned_user) + # get :new + # it_redirects_to_simple(user_path(banned_user)) + # expect(flash[:error]).to include("Your account has been banned.") + # end + # end + + # describe "create" do + # let(:user) { create(:user) } + + # before { fake_login_known_user(user) } + + # it "doesn't allow a user to create a work in a series that they don't own" do + # @series = create(:series) + # work_attributes = attributes_for(:work).except(:posted) + # work_attributes[:series_attributes] = { id: @series.id } + # expect { + # post :create, params: { work: work_attributes } + # }.not_to change { @series.works.all.count } + # expect(response).to render_template :new + # expect(assigns[:work].errors.full_messages).to \ + # include("You can't add a work to that series.") + # end + + # it "doesn't allow a user to submit only a pseud that is not theirs" do + # user2 = create(:user) + # work_attributes = attributes_for(:work).except(:posted) + # work_attributes[:author_attributes] = { ids: [user2.pseuds.first.id] } + # expect { + # post :create, params: { work: work_attributes } + # }.to_not change(Work, :count) + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include "You're not allowed to use that pseud." + # end + + # it "renders new if the work has invalid pseuds" do + # work_attributes = attributes_for(:work).except(:posted) + # work_attributes[:author_attributes] = { ids: user.pseud_ids, + # byline: "*impossible*" } + # post :create, params: { work: work_attributes } + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include "Invalid creator: Could not find a pseud *impossible*." + # end + + # it "renders new if the work has ambiguous pseuds" do + # create(:pseud, name: "ambiguous") + # create(:pseud, name: "ambiguous") + # work_attributes = attributes_for(:work).except(:posted) + # work_attributes[:author_attributes] = { ids: user.pseud_ids, + # byline: "ambiguous" } + # post :create, params: { work: work_attributes } + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include "Invalid creator: The pseud ambiguous is ambiguous." + # end + + # it "renders new if the work has noncanonical warnings" do + # work_attributes = attributes_for(:work).except(:posted, :archive_warning_string).merge(archive_warning_string: "Warning") + # post :create, params: { work: work_attributes } + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include /Only canonical warning tags are allowed./ + # end + + # it "renders new if the work has noncanonical rating" do + # work_attributes = attributes_for(:work).except(:posted, :rating_string).merge(rating_string: "Rating") + # post :create, params: { work: work_attributes } + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include /Only canonical rating tags are allowed./ + # end + + # it "renders new if the work has noncanonical category" do + # work_attributes = attributes_for(:work).except(:posted).merge(category_strings: ["Category"]) + # post :create, params: { work: work_attributes } + # expect(response).to render_template("new") + # expect(assigns[:work].errors.full_messages).to \ + # include /Only canonical category tags are allowed./ + # end + + # context "as a tag wrangler" do + # let(:user) { create(:tag_wrangler) } + + # it "does not set wrangling activity when posting with a new fandom" do + # work_attributes = attributes_for(:work).except(:posted, :fandom_string).merge(fandom_string: "New Fandom") + # post :create, params: { work: work_attributes } + # expect(user.last_wrangling_activity).to be_nil + # end + + # it "does not set wrangling activity when posting with an unsorted tag" do + # tag = create(:unsorted_tag) + # work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) + # post :create, params: { work: work_attributes } + # expect(user.last_wrangling_activity).to be_nil + # end + # end + + # it "errors and redirects to user page when user is banned" do + # fake_login_known_user(banned_user) + # tag = create(:unsorted_tag) + # work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) + # post :create, params: { work: work_attributes } + # it_redirects_to_simple(user_path(banned_user)) + # expect(flash[:error]).to include("Your account has been banned.") + # end + # end + + # describe "show" do + # let(:work) { create(:work) } + + # it "doesn't error when a work has no fandoms" do + # work_no_fandoms = build(:work, fandom_string: "") + # work_no_fandoms.save!(validate: false) + # fake_login + + # get :show, params: { id: work_no_fandoms.id } + + # expect(assigns(:page_title)).to include "No fandom specified" + # end + + # it "assigns @page_subtitle with unrevealed work and not @page_title" do + # work.update!(in_unrevealed_collection: true) + # get :show, params: { id: work.id } + # expect(assigns[:page_subtitle]).to eq("Mystery Work") + # expect(assigns[:page_title]).to be_nil + # end + + # context "when work does not exist" do + # it "raises an error" do + # expect do + # get :show, params: { id: "999999999" } + # end.to raise_error ActiveRecord::RecordNotFound + # end + # end + # end + + # describe "share" do + # it "returns a 404 response for unrevealed works" do + # unrevealed_collection = create :unrevealed_collection + # unrevealed_work = create :work, collections: [unrevealed_collection] + + # get :share, params: { id: unrevealed_work.id }, xhr: true + # expect(response.status).to eq(404) + # end + + # it "redirects to referer with an error for non-ajax warnings requests" do + # work = create(:work) + # referer = work_path(work) + # request.headers["HTTP_REFERER"] = referer + # get :share, params: { id: work.id } + # it_redirects_to_with_error(referer, "Sorry, you need to have JavaScript enabled for this.") + # end + # end + + # describe "index" do + # before do + # @fandom = create(:canonical_fandom) + # @work = create(:work, fandom_string: @fandom.name) + # end + + # it "returns the work" do + # get :index + # expect(assigns(:works)).to include(@work) + # end + + # it "sets the fandom when given a fandom id" do + # params = { fandom_id: @fandom.id } + # get :index, params: params + # expect(assigns(:fandom)).to eq(@fandom) + # end + + # describe "when the fandom id is invalid" do + # it "raises a 404 for an invalid id" do + # params = { fandom_id: 0 } + # expect { get :index, params: params } + # .to raise_error ActiveRecord::RecordNotFound + # end + # end + + # describe "when the fandom id is empty" do + # it "returns the work" do + # params = { fandom_id: nil } + # get :index, params: params + # expect(assigns(:works)).to include(@work) + # end + # end + + # describe "without caching" do + # before do + # AdminSetting.first.update_attribute(:enable_test_caching, false) + # end + + # it "returns the result with different works the second time" do + # get :index + # expect(assigns(:works)).to include(@work) + # work2 = create(:work) + # get :index + # expect(assigns(:works)).to include(work2) + # end + # end + + # describe "with caching" do + # before do + # AdminSetting.first.update_attribute(:enable_test_caching, true) + # end + + # context "with NO owner tag" do + # it "returns the same result the second time when a new work is created within the expiration time" do + # get :index + # expect(assigns(:works)).to include(@work) + # work2 = create(:work) + # run_all_indexing_jobs + # get :index + # expect(assigns(:works)).not_to include(work2) + # end + # end + + # context "with a valid owner tag" do + # before do + # @fandom2 = create(:canonical_fandom) + # @work2 = create(:work, fandom_string: @fandom2.name) + # run_all_indexing_jobs + # end + + # it "only gets works under that tag" do + # get :index, params: { tag_id: @fandom.name } + # expect(assigns(:works).items).to include(@work) + # expect(assigns(:works).items).not_to include(@work2) + # end + + # it "shows different results on second page" do + # get :index, params: { tag_id: @fandom.name, page: 2 } + # expect(assigns(:works).items).not_to include(@work) + # end + + # context "with restricted works" do + # before do + # @work2 = create(:work, fandom_string: @fandom.name, restricted: true) + # run_all_indexing_jobs + # end + + # it "shows restricted works to guests" do + # get :index, params: { tag_id: @fandom.name } + # expect(assigns(:works).items).to include(@work) + # expect(assigns(:works).items).not_to include(@work2) + # end + + # end + + # context "when tag is a synonym" do + # let(:fandom_synonym) { create(:fandom, merger: @fandom) } + + # it "redirects to the merger's work index" do + # params = { tag_id: fandom_synonym.name } + # get :index, params: params + # it_redirects_to tag_works_path(@fandom) + # end + + # context "when collection is specified" do + # let(:collection) { create(:collection) } + + # it "redirects to the merger's collection works index" do + # params = { tag_id: fandom_synonym.name, collection_id: collection.name } + # get :index, params: params + # it_redirects_to collection_tag_works_path(collection, @fandom) + # end + # end + # end + # end + # end + + # context "with an invalid owner tag" do + # it "raises an error" do + # params = { tag_id: "nonexistent_tag" } + # expect { get :index, params: params }.to raise_error( + # ActiveRecord::RecordNotFound, + # "Couldn't find tag named 'nonexistent_tag'" + # ) + # end + # end + + # context "with an invalid owner user" do + # it "raises an error" do + # params = { user_id: "nonexistent_user" } + # expect { get :index, params: params }.to raise_error( + # ActiveRecord::RecordNotFound + # ) + # end + + # context "with an invalid pseud" do + # it "raises an error" do + # params = { user_id: "nonexistent_user", pseud_id: "nonexistent_pseud" } + # expect { get :index, params: params }.to raise_error( + # ActiveRecord::RecordNotFound + # ) + # end + # end + # end + + # context "with a valid owner user" do + # let(:user) { create(:user) } + # let!(:user_work) { create(:work, authors: [user.default_pseud]) } + # let(:pseud) { create(:pseud, user: user) } + # let!(:pseud_work) { create(:work, authors: [pseud]) } + + # before { run_all_indexing_jobs } + + # it "includes only works for that user" do + # params = { user_id: user.login } + # get :index, params: params + # expect(assigns(:works).items).to include(user_work, pseud_work) + # expect(assigns(:works).items).not_to include(@work) + # end + + # context "with a valid pseud" do + # it "includes only works for that pseud" do + # params = { user_id: user.login, pseud_id: pseud.name } + # get :index, params: params + # expect(assigns(:works).items).to include(pseud_work) + # expect(assigns(:works).items).not_to include(user_work, @work) + # end + # end + + # context "with an invalid pseud" do + # it "includes all of that user's works" do + # params = { user_id: user.login, pseud_id: "nonexistent_pseud" } + # get :index, params: params + # expect(assigns(:works).items).to include(user_work, pseud_work) + # expect(assigns(:works).items).not_to include(@work) + # end + # end + # end + # end + + # describe "update" do + # let(:update_user) { create(:user) } + # let(:update_work) { + # work = create(:work, authors: [update_user.default_pseud]) + # create(:chapter, work: work) + # work + # } + + # before do + # fake_login_known_user(update_user) + # end + + # it "doesn't allow the user to add a series that they don't own" do + # @series = create(:series) + # attrs = { series_attributes: { id: @series.id } } + # expect { + # put :update, params: { id: update_work.id, work: attrs } + # }.not_to change { @series.works.all.count } + # expect(response).to render_template :edit + # expect(assigns[:work].errors.full_messages).to \ + # include("You can't add a work to that series.") + # end + + # it "redirects to the edit page if the work could not be saved" do + # allow_any_instance_of(Work).to receive(:save).and_return(false) + # update_work.fandom_string = "Testing" + # attrs = { title: "New Work Title" } + # put :update, params: { id: update_work.id, work: attrs } + # expect(response).to render_template :edit + # allow_any_instance_of(Work).to receive(:save).and_call_original + # end + + # it "updates the editor's pseuds for all chapters" do + # new_pseud = create(:pseud, user: update_user) + # put :update, params: { id: update_work.id, work: { author_attributes: { ids: [new_pseud.id] } } } + # expect(update_work.pseuds.reload).to contain_exactly(new_pseud) + # update_work.chapters.reload.each do |c| + # expect(c.pseuds.reload).to contain_exactly(new_pseud) + # end + # end + + # it "allows the user to invite co-creators" do + # co_creator = create(:user) + # co_creator.preference.update!(allow_cocreator: true) + # put :update, params: { id: update_work.id, work: { author_attributes: { byline: co_creator.login } } } + # expect(update_work.pseuds.reload).not_to include(co_creator.default_pseud) + # expect(update_work.user_has_creator_invite?(co_creator)).to be_truthy + # end + + # it "prevents inviting users who have disallowed co-creators" do + # no_co_creator = create(:user) + # no_co_creator.preference.update!(allow_cocreator: false) + # put :update, params: { id: update_work.id, work: { author_attributes: { byline: no_co_creator.login } } } + # expect(response).to render_template :edit + # expect(assigns[:work].errors.full_messages).to \ + # include "Invalid creator: #{no_co_creator.login} does not allow others to invite them to be a co-creator." + # expect(update_work.pseuds.reload).not_to include(no_co_creator.default_pseud) + # expect(update_work.user_has_creator_invite?(no_co_creator)).to be_falsey + # end + + # context "when the work has broken dates" do + # let(:update_work) { create(:work, authors: [update_user.default_pseud]) } + # let(:update_chapter) { update_work.first_chapter } + + # let(:attributes) do + # { + # backdate: "1", + # chapter_attributes: { + # published_at: "2021-09-01" + # } + # } + # end + + # before do + # # Work where chapter published_at did not override revised_at, times + # # taken from AO3-5392 + # update_work.update_column(:revised_at, Time.new(2018, 4, 22, 23, 51, 42, "+04:00")) + # update_chapter.update_column(:published_at, Date.new(2015, 7, 23)) + # end + + # it "can be backdated" do + # put :update, params: { id: update_work.id, work: attributes } + + # expect(update_chapter.reload.published_at).to eq(Date.new(2021, 9, 1)) + # expect(update_work.reload.revised_at).to eq(Time.utc(2021, 9, 1, 12)) # noon UTC + # end + # end + + # # If the time zone in config/application.rb is changed to something other + # # than the default (UTC), these tests will need adjusting: + # context "when redating to the present" do + # let!(:update_work) do + # # November 30, 2 PM UTC -- no time zone oddities here + # travel_to(Time.utc(2021, 11, 30, 14)) do + # create(:work, authors: [update_user.default_pseud]) + # end + # end + + # let(:attributes) do + # { + # backdate: "1", + # chapter_attributes: { + # published_at: "2021-12-05" + # } + # } + # end + + # before do + # travel_to(redate_time) + + # # Simulate the system time being UTC: + # allow(Time).to receive(:now).and_return(redate_time) + # allow(DateTime).to receive(:now).and_return(redate_time) + # allow(Date).to receive(:today).and_return(redate_time.to_date) + + # put :update, params: { id: update_work.id, work: attributes } + # end + + # context "before midnight UTC and after midnight Samara" do + # # December 5, 3 AM Europe/Samara (UTC+04:00) -- still December 4 in UTC + # let(:redate_time) { Time.new(2021, 12, 5, 3, 0, 0, "+04:00") } + + # it "prevents setting the publication date to the future" do + # expect(response).to render_template :edit + # expect(assigns[:work].errors.full_messages).to \ + # include("Publication date can't be in the future.") + # end + # end + + # context "before noon UTC" do + # # December 5, 6 AM Europe/Samara -- before noon, but after midnight in both time zones + # let(:redate_time) { Time.new(2021, 12, 5, 6, 0, 0, "+04:00") } + + # it "doesn't set revised_at to the future" do + # update_work.reload + # expect(update_work.revised_at).to be <= Time.current + # end + # end + # end + + # it "errors and redirects to user page when user is banned" do + # fake_login_known_user(banned_user) + # attrs = { title: "New Work Title" } + # put :update, params: { id: banned_users_work.id, work: attrs } + # it_redirects_to_simple(user_path(banned_user)) + # expect(flash[:error]).to include("Your account has been banned.") + # end + # end + + # describe "collected" do + # let(:collection) { create(:collection) } + # let(:collected_user) { create(:user) } + + # it "returns not found error if user does not exist" do + # expect do + # get :collected, params: { user_id: "dummyuser" } + # end.to raise_error(ActiveRecord::RecordNotFound) + # end + + # it "returns not found error if no user is set" do + # expect do + # get :collected + # end.to raise_error(ActiveRecord::RecordNotFound) + # end + + # context "with anonymous works" do + # let(:anonymous_collection) { create(:anonymous_collection) } + + # let!(:work) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: collection.name) + # end + + # let!(:anonymous_work) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: anonymous_collection.name) + # end + + # before { run_all_indexing_jobs } + + # it "does not return anonymous works in collections for guests" do + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work) + # expect(assigns(:works)).not_to include(anonymous_work) + # end + + # it "does not return anonymous works in collections for logged-in users" do + # fake_login + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work) + # expect(assigns(:works)).not_to include(anonymous_work) + # end + + # it "returns anonymous works in collections for the author" do + # fake_login_known_user(collected_user) + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work, anonymous_work) + # end + # end + + # context "with restricted works" do + # let(:collected_fandom) { create(:canonical_fandom) } + # let(:collected_fandom_2) { create(:canonical_fandom) } + + # let!(:unrestricted_work) do + # create(:work, + # authors: [collected_user.default_pseud], + # fandom_string: collected_fandom.name) + # end + + # let!(:unrestricted_work_in_collection) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # fandom_string: collected_fandom.name) + # end + + # let!(:unrestricted_work_2_in_collection) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # fandom_string: collected_fandom_2.name) + # end + + # let!(:restricted_work_in_collection) do + # create(:work, + # restricted: true, + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # fandom_string: collected_fandom.name) + # end + + # before { run_all_indexing_jobs } + + # context "as a guest" do + # it "renders the collected form" do + # get :collected, params: { user_id: collected_user.login } + # expect(response).to render_template("collected") + # end + + # it "returns ONLY unrestricted works in collections" do + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(unrestricted_work_in_collection, unrestricted_work_2_in_collection) + # expect(assigns(:works)).not_to include(unrestricted_work, restricted_work_in_collection) + # end + + # it "returns filtered works when search parameters are provided" do + # get :collected, params: { user_id: collected_user.login, work_search: { query: "fandom_ids:#{collected_fandom_2.id}" }} + # expect(assigns(:works)).to include(unrestricted_work_2_in_collection) + # expect(assigns(:works)).not_to include(unrestricted_work_in_collection) + # end + # end + + # context "with a logged-in user" do + # before { fake_login } + + # it "returns ONLY works in collections" do + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(unrestricted_work_in_collection, restricted_work_in_collection) + # expect(assigns(:works)).not_to include(unrestricted_work) + # end + # end + # end + + # context "with unrevealed works" do + # let(:unrevealed_collection) { create(:unrevealed_collection) } + + # let!(:work) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: collection.name) + # end + + # let!(:unrevealed_work) do + # create(:work, + # authors: [collected_user.default_pseud], + # collection_names: unrevealed_collection.name) + # end + + # before { run_all_indexing_jobs } + + # it "doesn't return unrevealed works in collections for guests" do + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work) + # expect(assigns(:works)).not_to include(unrevealed_work) + # end + + # it "doesn't return unrevealed works in collections for logged-in users" do + # fake_login + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work) + # expect(assigns(:works)).not_to include(unrevealed_work) + # end + + # it "returns unrevealed works in collections for the author" do + # fake_login_known_user(collected_user) + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works)).to include(work, unrevealed_work) + # end + # end + + # context "with sorting options" do + # let!(:new_work) do + # create(:work, + # title: "New Title", + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # created_at: 3.days.ago, + # revised_at: 3.days.ago) + # end + + # let!(:old_work) do + # create(:work, + # title: "Old Title", + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # created_at: 30.days.ago, + # revised_at: 30.days.ago) + # end + + # let!(:revised_work) do + # create(:work, + # title: "Revised Title", + # authors: [collected_user.default_pseud], + # collection_names: collection.name, + # created_at: 20.days.ago, + # revised_at: 2.days.ago) + # end + + # before { run_all_indexing_jobs } + + # it "sorts by date" do + # get :collected, params: { user_id: collected_user.login } + # expect(assigns(:works).map(&:title)).to eq([revised_work, new_work, old_work].map(&:title)) + # get :collected, params: { user_id: collected_user.login, work_search: { sort_direction: "asc" } } + # expect(assigns(:works).map(&:title)).to eq([old_work, new_work, revised_work].map(&:title)) + # end + + # it "sorts by title" do + # get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on" } } + # expect(assigns(:works).map(&:title)).to eq([new_work, old_work, revised_work].map(&:title)) + # get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on", sort_direction: "desc" } } + # expect(assigns(:works).map(&:title)).to eq([revised_work, old_work, new_work].map(&:title)) + # end + # end + # end describe "destroy" do let(:work) { create(:work) } @@ -883,6 +883,8 @@ def call_with_params(params) context "when a work has consecutive deleted comments in a thread" do before do + work.kudos.create(user: create(:user)) # Add a kudo to the work + thread_depth = 4 chapter = work.first_chapter @@ -900,12 +902,34 @@ def call_with_params(params) fake_login_known_user(work.users.first) end - it "deletes the work and redirects to the user's works with a notice" do + it "deletes the work and its associations, and redirects to the user's works with a notice" do delete :destroy, params: { id: work.id } it_redirects_to_with_notice(user_works_path(controller.current_user), "Your work #{work_title} was deleted.") expect { work.reload }.to raise_exception(ActiveRecord::RecordNotFound) + expect(Kudo.count).to eq(0) expect(Comment.count).to eq(0) + expect(InboxComment.count).to eq(0) + end + end + + context "when a work has multiple chapters" do + before do + first_chapter = work.first_chapter + second_chapter = create(:chapter, work: work, position: 2) + + comment = create(:comment, commentable: first_chapter, parent: first_chapter) + create(:comment, commentable: second_chapter, parent: second_chapter) + fake_login_known_user(work.users.first) + end + + it "deletes the work and its associations, and redirects to the user's works with a notice" do + delete :destroy, params: { id: work.id } + + it_redirects_to_with_notice(user_works_path(controller.current_user), "Your work #{work_title} was deleted.") + expect { work.reload }.to raise_exception(ActiveRecord::RecordNotFound) + expect(Comment.count).to eq(0) + expect(InboxComment.count).to eq(0) end end @@ -917,7 +941,7 @@ def call_with_params(params) it "errors and redirects to user page" do fake_login_known_user(suspended_user) delete :destroy, params: { id: suspended_users_work.id } - + it_redirects_to_simple(user_path(suspended_user)) expect(flash[:error]).to include("Your account has been suspended") end From 49102f65bc20470ba0d8888f76ddfc15f37e3d63 Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Tue, 7 Oct 2025 21:02:58 +0000 Subject: [PATCH 2/5] oops --- .../works/default_rails_actions_spec.rb | 1710 ++++++++--------- 1 file changed, 855 insertions(+), 855 deletions(-) diff --git a/spec/controllers/works/default_rails_actions_spec.rb b/spec/controllers/works/default_rails_actions_spec.rb index 981108c216f..2f48e6c40e3 100644 --- a/spec/controllers/works/default_rails_actions_spec.rb +++ b/spec/controllers/works/default_rails_actions_spec.rb @@ -21,861 +21,861 @@ work end - # describe "before_action #clean_work_search_params" do - # let(:params) { {} } - - # def call_with_params(params) - # controller.params = { work_search: params } - # controller.params[:work_search] = controller.clean_work_search_params - # end - - # context "when no work search parameters are given" do - # it "redirects to the login screen when no user is logged in" do - # get :clean_work_search_params, params: params - # it_redirects_to_with_error(new_user_session_path, - # "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") - # end - # end - - # context "when the query contains countable search parameters" do - # it "escapes less and greater than in query" do - # [ - # { params: "< 5 words", expected: "< 5 words", message: "Should escape <" }, - # { params: "> 5 words", expected: "> 5 words", message: "Should escape >" }, - # ].each do |settings| - # call_with_params(query: settings[:params]) - # expect(controller.params[:work_search][:query]) - # .to eq(settings[:expected]), settings[:message] - # end - # end - - # it "converts 'word' to 'word_count'" do - # call_with_params(query: "word:6") - # expect(controller.params[:work_search][:word_count]).to eq("6") - # end - - # it "converts 'words' to 'word_count'" do - # call_with_params(query: "words:7") - # expect(controller.params[:work_search][:word_count]).to eq("7") - # end - - # it "converts 'hits' queries to 'hits'" do - # call_with_params(query: "hits:8") - # expect(controller.params[:work_search][:hits]).to eq("8") - # end - - # it "converts other queries to (pluralized term)_count" do - # %w(kudo comment bookmark).each do |term| - # call_with_params(query: "#{term}:9") - # expect(controller.params[:work_search]["#{term.pluralize}_count"]) - # .to eq("9"), "Search term '#{term}' should become #{term.pluralize}_count key" - # end - # end - # end - - # context "when sort parameters are provided" do - # it "converts variations on 'sorted by: X' into :sort_column key" do - # [ - # "sort by: words", - # "sorted by: words", - # "sorted: words", - # "sort: words", - # "sort by < words", - # "sort by > words", - # "sort by = words" - # ].each do |query| - # call_with_params(query: query) - # expect(controller.params[:work_search][:sort_column]) - # .to eq("word_count"), "Sort command '#{query}' should be converted to :sort_column" - # end - # end - - # it "converts variations on sort columns to column name" do - # [ - # { query: "sort by: word count", expected: "word_count" }, - # { query: "sort by: words", expected: "word_count" }, - # { query: "sort by: word", expected: "word_count" }, - # { query: "sort by: creator", expected: "authors_to_sort_on" }, - # { query: "sort by: title", expected: "title_to_sort_on" }, - # { query: "sort by: date", expected: "created_at" }, - # { query: "sort by: date posted", expected: "created_at" }, - # { query: "sort by: hits", expected: "hits" }, - # { query: "sort by: kudos", expected: "kudos_count" }, - # { query: "sort by: comments", expected: "comments_count" }, - # { query: "sort by: bookmarks", expected: "bookmarks_count" }, - # ].each do |settings| - # call_with_params(query: settings[:query]) - # actual = controller.params[:work_search][:sort_column] - # expect(actual) - # .to eq(settings[:expected]), - # "Query '#{settings[:query]}' should be converted to :sort_column '#{settings[:expected]}' but is '#{actual}'" - # end - # end - - # it "converts 'ascending' or '>' into :sort_direction key 'asc'" do - # [ - # "sort > word_count", - # "sort: word_count ascending", - # "sort: hits ascending", - # ].each do |query| - # call_with_params(query: query) - # expect(controller.params[:work_search][:sort_direction]).to eq("asc") - # end - # end - - # it "converts 'descending' or '<' into :sort_direction key 'desc'" do - # [ - # "sort < word_count", - # "sort: word_count descending", - # "sort: hits descending", - # ].each do |query| - # call_with_params(query: query) - # expect(controller.params[:work_search][:sort_direction]).to eq("desc") - # end - # end - - # # The rest of these are probably bugs - # it "returns no sort column if there is NO punctuation after 'sort by' clause" do - # call_with_params(query: "sort by word count") - # expect(controller.params[:work_search][:sort_column]).to be_nil - # end - - # it "can't search by date updated" do - # [ - # { query: "sort by: date updated", expected: "revised_at" }, - # ].each do |settings| - # call_with_params(query: settings[:query]) - # expect(controller.params[:work_search][:sort_column]).to eq("created_at") # should be revised_at - # end - # end - - # it "can't sort ascending if more than one word follows the colon" do - # [ - # "sort by: word count ascending", - # ].each do |query| - # call_with_params(query: query) - # expect(controller.params[:work_search][:sort_direction]).to be_nil - # end - # end - # end - - # context "when the query contains categories" do - # it "surrounds categories in quotes" do - # [ - # { query: "M/F sort by: comments", expected: "\"m/f\"" }, - # { query: "f/f Scully/Reyes", expected: "\"f/f\" Scully/Reyes" }, - # ].each do |settings| - # call_with_params(query: settings[:query]) - # expect(controller.params[:work_search][:query]).to eq(settings[:expected]) - # end - # end - - # it "does not surround categories in quotes when it shouldn't" do - # query = "sam/frodo sort by: word" - # call_with_params(query: query) - # expect(controller.params[:work_search][:query]).to eq("sam/frodo") - # end - # end - # end - - # describe "new" do - # it "doesn't return the form for anyone not logged in" do - # get :new - # it_redirects_to_with_error(new_user_session_path, - # "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") - # end - - # it "renders the form if logged in" do - # fake_login - # get :new - # expect(response).to render_template("new") - # end - - # it "errors and redirects to user page when user is banned" do - # fake_login_known_user(banned_user) - # get :new - # it_redirects_to_simple(user_path(banned_user)) - # expect(flash[:error]).to include("Your account has been banned.") - # end - # end - - # describe "create" do - # let(:user) { create(:user) } - - # before { fake_login_known_user(user) } - - # it "doesn't allow a user to create a work in a series that they don't own" do - # @series = create(:series) - # work_attributes = attributes_for(:work).except(:posted) - # work_attributes[:series_attributes] = { id: @series.id } - # expect { - # post :create, params: { work: work_attributes } - # }.not_to change { @series.works.all.count } - # expect(response).to render_template :new - # expect(assigns[:work].errors.full_messages).to \ - # include("You can't add a work to that series.") - # end - - # it "doesn't allow a user to submit only a pseud that is not theirs" do - # user2 = create(:user) - # work_attributes = attributes_for(:work).except(:posted) - # work_attributes[:author_attributes] = { ids: [user2.pseuds.first.id] } - # expect { - # post :create, params: { work: work_attributes } - # }.to_not change(Work, :count) - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include "You're not allowed to use that pseud." - # end - - # it "renders new if the work has invalid pseuds" do - # work_attributes = attributes_for(:work).except(:posted) - # work_attributes[:author_attributes] = { ids: user.pseud_ids, - # byline: "*impossible*" } - # post :create, params: { work: work_attributes } - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include "Invalid creator: Could not find a pseud *impossible*." - # end - - # it "renders new if the work has ambiguous pseuds" do - # create(:pseud, name: "ambiguous") - # create(:pseud, name: "ambiguous") - # work_attributes = attributes_for(:work).except(:posted) - # work_attributes[:author_attributes] = { ids: user.pseud_ids, - # byline: "ambiguous" } - # post :create, params: { work: work_attributes } - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include "Invalid creator: The pseud ambiguous is ambiguous." - # end - - # it "renders new if the work has noncanonical warnings" do - # work_attributes = attributes_for(:work).except(:posted, :archive_warning_string).merge(archive_warning_string: "Warning") - # post :create, params: { work: work_attributes } - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include /Only canonical warning tags are allowed./ - # end - - # it "renders new if the work has noncanonical rating" do - # work_attributes = attributes_for(:work).except(:posted, :rating_string).merge(rating_string: "Rating") - # post :create, params: { work: work_attributes } - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include /Only canonical rating tags are allowed./ - # end - - # it "renders new if the work has noncanonical category" do - # work_attributes = attributes_for(:work).except(:posted).merge(category_strings: ["Category"]) - # post :create, params: { work: work_attributes } - # expect(response).to render_template("new") - # expect(assigns[:work].errors.full_messages).to \ - # include /Only canonical category tags are allowed./ - # end - - # context "as a tag wrangler" do - # let(:user) { create(:tag_wrangler) } - - # it "does not set wrangling activity when posting with a new fandom" do - # work_attributes = attributes_for(:work).except(:posted, :fandom_string).merge(fandom_string: "New Fandom") - # post :create, params: { work: work_attributes } - # expect(user.last_wrangling_activity).to be_nil - # end - - # it "does not set wrangling activity when posting with an unsorted tag" do - # tag = create(:unsorted_tag) - # work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) - # post :create, params: { work: work_attributes } - # expect(user.last_wrangling_activity).to be_nil - # end - # end - - # it "errors and redirects to user page when user is banned" do - # fake_login_known_user(banned_user) - # tag = create(:unsorted_tag) - # work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) - # post :create, params: { work: work_attributes } - # it_redirects_to_simple(user_path(banned_user)) - # expect(flash[:error]).to include("Your account has been banned.") - # end - # end - - # describe "show" do - # let(:work) { create(:work) } - - # it "doesn't error when a work has no fandoms" do - # work_no_fandoms = build(:work, fandom_string: "") - # work_no_fandoms.save!(validate: false) - # fake_login - - # get :show, params: { id: work_no_fandoms.id } - - # expect(assigns(:page_title)).to include "No fandom specified" - # end - - # it "assigns @page_subtitle with unrevealed work and not @page_title" do - # work.update!(in_unrevealed_collection: true) - # get :show, params: { id: work.id } - # expect(assigns[:page_subtitle]).to eq("Mystery Work") - # expect(assigns[:page_title]).to be_nil - # end - - # context "when work does not exist" do - # it "raises an error" do - # expect do - # get :show, params: { id: "999999999" } - # end.to raise_error ActiveRecord::RecordNotFound - # end - # end - # end - - # describe "share" do - # it "returns a 404 response for unrevealed works" do - # unrevealed_collection = create :unrevealed_collection - # unrevealed_work = create :work, collections: [unrevealed_collection] - - # get :share, params: { id: unrevealed_work.id }, xhr: true - # expect(response.status).to eq(404) - # end - - # it "redirects to referer with an error for non-ajax warnings requests" do - # work = create(:work) - # referer = work_path(work) - # request.headers["HTTP_REFERER"] = referer - # get :share, params: { id: work.id } - # it_redirects_to_with_error(referer, "Sorry, you need to have JavaScript enabled for this.") - # end - # end - - # describe "index" do - # before do - # @fandom = create(:canonical_fandom) - # @work = create(:work, fandom_string: @fandom.name) - # end - - # it "returns the work" do - # get :index - # expect(assigns(:works)).to include(@work) - # end - - # it "sets the fandom when given a fandom id" do - # params = { fandom_id: @fandom.id } - # get :index, params: params - # expect(assigns(:fandom)).to eq(@fandom) - # end - - # describe "when the fandom id is invalid" do - # it "raises a 404 for an invalid id" do - # params = { fandom_id: 0 } - # expect { get :index, params: params } - # .to raise_error ActiveRecord::RecordNotFound - # end - # end - - # describe "when the fandom id is empty" do - # it "returns the work" do - # params = { fandom_id: nil } - # get :index, params: params - # expect(assigns(:works)).to include(@work) - # end - # end - - # describe "without caching" do - # before do - # AdminSetting.first.update_attribute(:enable_test_caching, false) - # end - - # it "returns the result with different works the second time" do - # get :index - # expect(assigns(:works)).to include(@work) - # work2 = create(:work) - # get :index - # expect(assigns(:works)).to include(work2) - # end - # end - - # describe "with caching" do - # before do - # AdminSetting.first.update_attribute(:enable_test_caching, true) - # end - - # context "with NO owner tag" do - # it "returns the same result the second time when a new work is created within the expiration time" do - # get :index - # expect(assigns(:works)).to include(@work) - # work2 = create(:work) - # run_all_indexing_jobs - # get :index - # expect(assigns(:works)).not_to include(work2) - # end - # end - - # context "with a valid owner tag" do - # before do - # @fandom2 = create(:canonical_fandom) - # @work2 = create(:work, fandom_string: @fandom2.name) - # run_all_indexing_jobs - # end - - # it "only gets works under that tag" do - # get :index, params: { tag_id: @fandom.name } - # expect(assigns(:works).items).to include(@work) - # expect(assigns(:works).items).not_to include(@work2) - # end - - # it "shows different results on second page" do - # get :index, params: { tag_id: @fandom.name, page: 2 } - # expect(assigns(:works).items).not_to include(@work) - # end - - # context "with restricted works" do - # before do - # @work2 = create(:work, fandom_string: @fandom.name, restricted: true) - # run_all_indexing_jobs - # end - - # it "shows restricted works to guests" do - # get :index, params: { tag_id: @fandom.name } - # expect(assigns(:works).items).to include(@work) - # expect(assigns(:works).items).not_to include(@work2) - # end - - # end - - # context "when tag is a synonym" do - # let(:fandom_synonym) { create(:fandom, merger: @fandom) } - - # it "redirects to the merger's work index" do - # params = { tag_id: fandom_synonym.name } - # get :index, params: params - # it_redirects_to tag_works_path(@fandom) - # end - - # context "when collection is specified" do - # let(:collection) { create(:collection) } - - # it "redirects to the merger's collection works index" do - # params = { tag_id: fandom_synonym.name, collection_id: collection.name } - # get :index, params: params - # it_redirects_to collection_tag_works_path(collection, @fandom) - # end - # end - # end - # end - # end - - # context "with an invalid owner tag" do - # it "raises an error" do - # params = { tag_id: "nonexistent_tag" } - # expect { get :index, params: params }.to raise_error( - # ActiveRecord::RecordNotFound, - # "Couldn't find tag named 'nonexistent_tag'" - # ) - # end - # end - - # context "with an invalid owner user" do - # it "raises an error" do - # params = { user_id: "nonexistent_user" } - # expect { get :index, params: params }.to raise_error( - # ActiveRecord::RecordNotFound - # ) - # end - - # context "with an invalid pseud" do - # it "raises an error" do - # params = { user_id: "nonexistent_user", pseud_id: "nonexistent_pseud" } - # expect { get :index, params: params }.to raise_error( - # ActiveRecord::RecordNotFound - # ) - # end - # end - # end - - # context "with a valid owner user" do - # let(:user) { create(:user) } - # let!(:user_work) { create(:work, authors: [user.default_pseud]) } - # let(:pseud) { create(:pseud, user: user) } - # let!(:pseud_work) { create(:work, authors: [pseud]) } - - # before { run_all_indexing_jobs } - - # it "includes only works for that user" do - # params = { user_id: user.login } - # get :index, params: params - # expect(assigns(:works).items).to include(user_work, pseud_work) - # expect(assigns(:works).items).not_to include(@work) - # end - - # context "with a valid pseud" do - # it "includes only works for that pseud" do - # params = { user_id: user.login, pseud_id: pseud.name } - # get :index, params: params - # expect(assigns(:works).items).to include(pseud_work) - # expect(assigns(:works).items).not_to include(user_work, @work) - # end - # end - - # context "with an invalid pseud" do - # it "includes all of that user's works" do - # params = { user_id: user.login, pseud_id: "nonexistent_pseud" } - # get :index, params: params - # expect(assigns(:works).items).to include(user_work, pseud_work) - # expect(assigns(:works).items).not_to include(@work) - # end - # end - # end - # end - - # describe "update" do - # let(:update_user) { create(:user) } - # let(:update_work) { - # work = create(:work, authors: [update_user.default_pseud]) - # create(:chapter, work: work) - # work - # } - - # before do - # fake_login_known_user(update_user) - # end - - # it "doesn't allow the user to add a series that they don't own" do - # @series = create(:series) - # attrs = { series_attributes: { id: @series.id } } - # expect { - # put :update, params: { id: update_work.id, work: attrs } - # }.not_to change { @series.works.all.count } - # expect(response).to render_template :edit - # expect(assigns[:work].errors.full_messages).to \ - # include("You can't add a work to that series.") - # end - - # it "redirects to the edit page if the work could not be saved" do - # allow_any_instance_of(Work).to receive(:save).and_return(false) - # update_work.fandom_string = "Testing" - # attrs = { title: "New Work Title" } - # put :update, params: { id: update_work.id, work: attrs } - # expect(response).to render_template :edit - # allow_any_instance_of(Work).to receive(:save).and_call_original - # end - - # it "updates the editor's pseuds for all chapters" do - # new_pseud = create(:pseud, user: update_user) - # put :update, params: { id: update_work.id, work: { author_attributes: { ids: [new_pseud.id] } } } - # expect(update_work.pseuds.reload).to contain_exactly(new_pseud) - # update_work.chapters.reload.each do |c| - # expect(c.pseuds.reload).to contain_exactly(new_pseud) - # end - # end - - # it "allows the user to invite co-creators" do - # co_creator = create(:user) - # co_creator.preference.update!(allow_cocreator: true) - # put :update, params: { id: update_work.id, work: { author_attributes: { byline: co_creator.login } } } - # expect(update_work.pseuds.reload).not_to include(co_creator.default_pseud) - # expect(update_work.user_has_creator_invite?(co_creator)).to be_truthy - # end - - # it "prevents inviting users who have disallowed co-creators" do - # no_co_creator = create(:user) - # no_co_creator.preference.update!(allow_cocreator: false) - # put :update, params: { id: update_work.id, work: { author_attributes: { byline: no_co_creator.login } } } - # expect(response).to render_template :edit - # expect(assigns[:work].errors.full_messages).to \ - # include "Invalid creator: #{no_co_creator.login} does not allow others to invite them to be a co-creator." - # expect(update_work.pseuds.reload).not_to include(no_co_creator.default_pseud) - # expect(update_work.user_has_creator_invite?(no_co_creator)).to be_falsey - # end - - # context "when the work has broken dates" do - # let(:update_work) { create(:work, authors: [update_user.default_pseud]) } - # let(:update_chapter) { update_work.first_chapter } - - # let(:attributes) do - # { - # backdate: "1", - # chapter_attributes: { - # published_at: "2021-09-01" - # } - # } - # end - - # before do - # # Work where chapter published_at did not override revised_at, times - # # taken from AO3-5392 - # update_work.update_column(:revised_at, Time.new(2018, 4, 22, 23, 51, 42, "+04:00")) - # update_chapter.update_column(:published_at, Date.new(2015, 7, 23)) - # end - - # it "can be backdated" do - # put :update, params: { id: update_work.id, work: attributes } - - # expect(update_chapter.reload.published_at).to eq(Date.new(2021, 9, 1)) - # expect(update_work.reload.revised_at).to eq(Time.utc(2021, 9, 1, 12)) # noon UTC - # end - # end - - # # If the time zone in config/application.rb is changed to something other - # # than the default (UTC), these tests will need adjusting: - # context "when redating to the present" do - # let!(:update_work) do - # # November 30, 2 PM UTC -- no time zone oddities here - # travel_to(Time.utc(2021, 11, 30, 14)) do - # create(:work, authors: [update_user.default_pseud]) - # end - # end - - # let(:attributes) do - # { - # backdate: "1", - # chapter_attributes: { - # published_at: "2021-12-05" - # } - # } - # end - - # before do - # travel_to(redate_time) - - # # Simulate the system time being UTC: - # allow(Time).to receive(:now).and_return(redate_time) - # allow(DateTime).to receive(:now).and_return(redate_time) - # allow(Date).to receive(:today).and_return(redate_time.to_date) - - # put :update, params: { id: update_work.id, work: attributes } - # end - - # context "before midnight UTC and after midnight Samara" do - # # December 5, 3 AM Europe/Samara (UTC+04:00) -- still December 4 in UTC - # let(:redate_time) { Time.new(2021, 12, 5, 3, 0, 0, "+04:00") } - - # it "prevents setting the publication date to the future" do - # expect(response).to render_template :edit - # expect(assigns[:work].errors.full_messages).to \ - # include("Publication date can't be in the future.") - # end - # end - - # context "before noon UTC" do - # # December 5, 6 AM Europe/Samara -- before noon, but after midnight in both time zones - # let(:redate_time) { Time.new(2021, 12, 5, 6, 0, 0, "+04:00") } - - # it "doesn't set revised_at to the future" do - # update_work.reload - # expect(update_work.revised_at).to be <= Time.current - # end - # end - # end - - # it "errors and redirects to user page when user is banned" do - # fake_login_known_user(banned_user) - # attrs = { title: "New Work Title" } - # put :update, params: { id: banned_users_work.id, work: attrs } - # it_redirects_to_simple(user_path(banned_user)) - # expect(flash[:error]).to include("Your account has been banned.") - # end - # end - - # describe "collected" do - # let(:collection) { create(:collection) } - # let(:collected_user) { create(:user) } - - # it "returns not found error if user does not exist" do - # expect do - # get :collected, params: { user_id: "dummyuser" } - # end.to raise_error(ActiveRecord::RecordNotFound) - # end - - # it "returns not found error if no user is set" do - # expect do - # get :collected - # end.to raise_error(ActiveRecord::RecordNotFound) - # end - - # context "with anonymous works" do - # let(:anonymous_collection) { create(:anonymous_collection) } - - # let!(:work) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: collection.name) - # end - - # let!(:anonymous_work) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: anonymous_collection.name) - # end - - # before { run_all_indexing_jobs } - - # it "does not return anonymous works in collections for guests" do - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work) - # expect(assigns(:works)).not_to include(anonymous_work) - # end - - # it "does not return anonymous works in collections for logged-in users" do - # fake_login - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work) - # expect(assigns(:works)).not_to include(anonymous_work) - # end - - # it "returns anonymous works in collections for the author" do - # fake_login_known_user(collected_user) - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work, anonymous_work) - # end - # end - - # context "with restricted works" do - # let(:collected_fandom) { create(:canonical_fandom) } - # let(:collected_fandom_2) { create(:canonical_fandom) } - - # let!(:unrestricted_work) do - # create(:work, - # authors: [collected_user.default_pseud], - # fandom_string: collected_fandom.name) - # end - - # let!(:unrestricted_work_in_collection) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # fandom_string: collected_fandom.name) - # end - - # let!(:unrestricted_work_2_in_collection) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # fandom_string: collected_fandom_2.name) - # end - - # let!(:restricted_work_in_collection) do - # create(:work, - # restricted: true, - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # fandom_string: collected_fandom.name) - # end - - # before { run_all_indexing_jobs } - - # context "as a guest" do - # it "renders the collected form" do - # get :collected, params: { user_id: collected_user.login } - # expect(response).to render_template("collected") - # end - - # it "returns ONLY unrestricted works in collections" do - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(unrestricted_work_in_collection, unrestricted_work_2_in_collection) - # expect(assigns(:works)).not_to include(unrestricted_work, restricted_work_in_collection) - # end - - # it "returns filtered works when search parameters are provided" do - # get :collected, params: { user_id: collected_user.login, work_search: { query: "fandom_ids:#{collected_fandom_2.id}" }} - # expect(assigns(:works)).to include(unrestricted_work_2_in_collection) - # expect(assigns(:works)).not_to include(unrestricted_work_in_collection) - # end - # end - - # context "with a logged-in user" do - # before { fake_login } - - # it "returns ONLY works in collections" do - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(unrestricted_work_in_collection, restricted_work_in_collection) - # expect(assigns(:works)).not_to include(unrestricted_work) - # end - # end - # end - - # context "with unrevealed works" do - # let(:unrevealed_collection) { create(:unrevealed_collection) } - - # let!(:work) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: collection.name) - # end - - # let!(:unrevealed_work) do - # create(:work, - # authors: [collected_user.default_pseud], - # collection_names: unrevealed_collection.name) - # end - - # before { run_all_indexing_jobs } - - # it "doesn't return unrevealed works in collections for guests" do - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work) - # expect(assigns(:works)).not_to include(unrevealed_work) - # end - - # it "doesn't return unrevealed works in collections for logged-in users" do - # fake_login - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work) - # expect(assigns(:works)).not_to include(unrevealed_work) - # end - - # it "returns unrevealed works in collections for the author" do - # fake_login_known_user(collected_user) - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works)).to include(work, unrevealed_work) - # end - # end - - # context "with sorting options" do - # let!(:new_work) do - # create(:work, - # title: "New Title", - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # created_at: 3.days.ago, - # revised_at: 3.days.ago) - # end - - # let!(:old_work) do - # create(:work, - # title: "Old Title", - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # created_at: 30.days.ago, - # revised_at: 30.days.ago) - # end - - # let!(:revised_work) do - # create(:work, - # title: "Revised Title", - # authors: [collected_user.default_pseud], - # collection_names: collection.name, - # created_at: 20.days.ago, - # revised_at: 2.days.ago) - # end - - # before { run_all_indexing_jobs } - - # it "sorts by date" do - # get :collected, params: { user_id: collected_user.login } - # expect(assigns(:works).map(&:title)).to eq([revised_work, new_work, old_work].map(&:title)) - # get :collected, params: { user_id: collected_user.login, work_search: { sort_direction: "asc" } } - # expect(assigns(:works).map(&:title)).to eq([old_work, new_work, revised_work].map(&:title)) - # end - - # it "sorts by title" do - # get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on" } } - # expect(assigns(:works).map(&:title)).to eq([new_work, old_work, revised_work].map(&:title)) - # get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on", sort_direction: "desc" } } - # expect(assigns(:works).map(&:title)).to eq([revised_work, old_work, new_work].map(&:title)) - # end - # end - # end + describe "before_action #clean_work_search_params" do + let(:params) { {} } + + def call_with_params(params) + controller.params = { work_search: params } + controller.params[:work_search] = controller.clean_work_search_params + end + + context "when no work search parameters are given" do + it "redirects to the login screen when no user is logged in" do + get :clean_work_search_params, params: params + it_redirects_to_with_error(new_user_session_path, + "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") + end + end + + context "when the query contains countable search parameters" do + it "escapes less and greater than in query" do + [ + { params: "< 5 words", expected: "< 5 words", message: "Should escape <" }, + { params: "> 5 words", expected: "> 5 words", message: "Should escape >" }, + ].each do |settings| + call_with_params(query: settings[:params]) + expect(controller.params[:work_search][:query]) + .to eq(settings[:expected]), settings[:message] + end + end + + it "converts 'word' to 'word_count'" do + call_with_params(query: "word:6") + expect(controller.params[:work_search][:word_count]).to eq("6") + end + + it "converts 'words' to 'word_count'" do + call_with_params(query: "words:7") + expect(controller.params[:work_search][:word_count]).to eq("7") + end + + it "converts 'hits' queries to 'hits'" do + call_with_params(query: "hits:8") + expect(controller.params[:work_search][:hits]).to eq("8") + end + + it "converts other queries to (pluralized term)_count" do + %w(kudo comment bookmark).each do |term| + call_with_params(query: "#{term}:9") + expect(controller.params[:work_search]["#{term.pluralize}_count"]) + .to eq("9"), "Search term '#{term}' should become #{term.pluralize}_count key" + end + end + end + + context "when sort parameters are provided" do + it "converts variations on 'sorted by: X' into :sort_column key" do + [ + "sort by: words", + "sorted by: words", + "sorted: words", + "sort: words", + "sort by < words", + "sort by > words", + "sort by = words" + ].each do |query| + call_with_params(query: query) + expect(controller.params[:work_search][:sort_column]) + .to eq("word_count"), "Sort command '#{query}' should be converted to :sort_column" + end + end + + it "converts variations on sort columns to column name" do + [ + { query: "sort by: word count", expected: "word_count" }, + { query: "sort by: words", expected: "word_count" }, + { query: "sort by: word", expected: "word_count" }, + { query: "sort by: creator", expected: "authors_to_sort_on" }, + { query: "sort by: title", expected: "title_to_sort_on" }, + { query: "sort by: date", expected: "created_at" }, + { query: "sort by: date posted", expected: "created_at" }, + { query: "sort by: hits", expected: "hits" }, + { query: "sort by: kudos", expected: "kudos_count" }, + { query: "sort by: comments", expected: "comments_count" }, + { query: "sort by: bookmarks", expected: "bookmarks_count" }, + ].each do |settings| + call_with_params(query: settings[:query]) + actual = controller.params[:work_search][:sort_column] + expect(actual) + .to eq(settings[:expected]), + "Query '#{settings[:query]}' should be converted to :sort_column '#{settings[:expected]}' but is '#{actual}'" + end + end + + it "converts 'ascending' or '>' into :sort_direction key 'asc'" do + [ + "sort > word_count", + "sort: word_count ascending", + "sort: hits ascending", + ].each do |query| + call_with_params(query: query) + expect(controller.params[:work_search][:sort_direction]).to eq("asc") + end + end + + it "converts 'descending' or '<' into :sort_direction key 'desc'" do + [ + "sort < word_count", + "sort: word_count descending", + "sort: hits descending", + ].each do |query| + call_with_params(query: query) + expect(controller.params[:work_search][:sort_direction]).to eq("desc") + end + end + + # The rest of these are probably bugs + it "returns no sort column if there is NO punctuation after 'sort by' clause" do + call_with_params(query: "sort by word count") + expect(controller.params[:work_search][:sort_column]).to be_nil + end + + it "can't search by date updated" do + [ + { query: "sort by: date updated", expected: "revised_at" }, + ].each do |settings| + call_with_params(query: settings[:query]) + expect(controller.params[:work_search][:sort_column]).to eq("created_at") # should be revised_at + end + end + + it "can't sort ascending if more than one word follows the colon" do + [ + "sort by: word count ascending", + ].each do |query| + call_with_params(query: query) + expect(controller.params[:work_search][:sort_direction]).to be_nil + end + end + end + + context "when the query contains categories" do + it "surrounds categories in quotes" do + [ + { query: "M/F sort by: comments", expected: "\"m/f\"" }, + { query: "f/f Scully/Reyes", expected: "\"f/f\" Scully/Reyes" }, + ].each do |settings| + call_with_params(query: settings[:query]) + expect(controller.params[:work_search][:query]).to eq(settings[:expected]) + end + end + + it "does not surround categories in quotes when it shouldn't" do + query = "sam/frodo sort by: word" + call_with_params(query: query) + expect(controller.params[:work_search][:query]).to eq("sam/frodo") + end + end + end + + describe "new" do + it "doesn't return the form for anyone not logged in" do + get :new + it_redirects_to_with_error(new_user_session_path, + "Sorry, you don't have permission to access the page you were trying to reach. Please log in.") + end + + it "renders the form if logged in" do + fake_login + get :new + expect(response).to render_template("new") + end + + it "errors and redirects to user page when user is banned" do + fake_login_known_user(banned_user) + get :new + it_redirects_to_simple(user_path(banned_user)) + expect(flash[:error]).to include("Your account has been banned.") + end + end + + describe "create" do + let(:user) { create(:user) } + + before { fake_login_known_user(user) } + + it "doesn't allow a user to create a work in a series that they don't own" do + @series = create(:series) + work_attributes = attributes_for(:work).except(:posted) + work_attributes[:series_attributes] = { id: @series.id } + expect { + post :create, params: { work: work_attributes } + }.not_to change { @series.works.all.count } + expect(response).to render_template :new + expect(assigns[:work].errors.full_messages).to \ + include("You can't add a work to that series.") + end + + it "doesn't allow a user to submit only a pseud that is not theirs" do + user2 = create(:user) + work_attributes = attributes_for(:work).except(:posted) + work_attributes[:author_attributes] = { ids: [user2.pseuds.first.id] } + expect { + post :create, params: { work: work_attributes } + }.to_not change(Work, :count) + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include "You're not allowed to use that pseud." + end + + it "renders new if the work has invalid pseuds" do + work_attributes = attributes_for(:work).except(:posted) + work_attributes[:author_attributes] = { ids: user.pseud_ids, + byline: "*impossible*" } + post :create, params: { work: work_attributes } + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include "Invalid creator: Could not find a pseud *impossible*." + end + + it "renders new if the work has ambiguous pseuds" do + create(:pseud, name: "ambiguous") + create(:pseud, name: "ambiguous") + work_attributes = attributes_for(:work).except(:posted) + work_attributes[:author_attributes] = { ids: user.pseud_ids, + byline: "ambiguous" } + post :create, params: { work: work_attributes } + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include "Invalid creator: The pseud ambiguous is ambiguous." + end + + it "renders new if the work has noncanonical warnings" do + work_attributes = attributes_for(:work).except(:posted, :archive_warning_string).merge(archive_warning_string: "Warning") + post :create, params: { work: work_attributes } + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include /Only canonical warning tags are allowed./ + end + + it "renders new if the work has noncanonical rating" do + work_attributes = attributes_for(:work).except(:posted, :rating_string).merge(rating_string: "Rating") + post :create, params: { work: work_attributes } + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include /Only canonical rating tags are allowed./ + end + + it "renders new if the work has noncanonical category" do + work_attributes = attributes_for(:work).except(:posted).merge(category_strings: ["Category"]) + post :create, params: { work: work_attributes } + expect(response).to render_template("new") + expect(assigns[:work].errors.full_messages).to \ + include /Only canonical category tags are allowed./ + end + + context "as a tag wrangler" do + let(:user) { create(:tag_wrangler) } + + it "does not set wrangling activity when posting with a new fandom" do + work_attributes = attributes_for(:work).except(:posted, :fandom_string).merge(fandom_string: "New Fandom") + post :create, params: { work: work_attributes } + expect(user.last_wrangling_activity).to be_nil + end + + it "does not set wrangling activity when posting with an unsorted tag" do + tag = create(:unsorted_tag) + work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) + post :create, params: { work: work_attributes } + expect(user.last_wrangling_activity).to be_nil + end + end + + it "errors and redirects to user page when user is banned" do + fake_login_known_user(banned_user) + tag = create(:unsorted_tag) + work_attributes = attributes_for(:work).except(:posted, :freeform_string).merge(freeform_string: tag.name) + post :create, params: { work: work_attributes } + it_redirects_to_simple(user_path(banned_user)) + expect(flash[:error]).to include("Your account has been banned.") + end + end + + describe "show" do + let(:work) { create(:work) } + + it "doesn't error when a work has no fandoms" do + work_no_fandoms = build(:work, fandom_string: "") + work_no_fandoms.save!(validate: false) + fake_login + + get :show, params: { id: work_no_fandoms.id } + + expect(assigns(:page_title)).to include "No fandom specified" + end + + it "assigns @page_subtitle with unrevealed work and not @page_title" do + work.update!(in_unrevealed_collection: true) + get :show, params: { id: work.id } + expect(assigns[:page_subtitle]).to eq("Mystery Work") + expect(assigns[:page_title]).to be_nil + end + + context "when work does not exist" do + it "raises an error" do + expect do + get :show, params: { id: "999999999" } + end.to raise_error ActiveRecord::RecordNotFound + end + end + end + + describe "share" do + it "returns a 404 response for unrevealed works" do + unrevealed_collection = create :unrevealed_collection + unrevealed_work = create :work, collections: [unrevealed_collection] + + get :share, params: { id: unrevealed_work.id }, xhr: true + expect(response.status).to eq(404) + end + + it "redirects to referer with an error for non-ajax warnings requests" do + work = create(:work) + referer = work_path(work) + request.headers["HTTP_REFERER"] = referer + get :share, params: { id: work.id } + it_redirects_to_with_error(referer, "Sorry, you need to have JavaScript enabled for this.") + end + end + + describe "index" do + before do + @fandom = create(:canonical_fandom) + @work = create(:work, fandom_string: @fandom.name) + end + + it "returns the work" do + get :index + expect(assigns(:works)).to include(@work) + end + + it "sets the fandom when given a fandom id" do + params = { fandom_id: @fandom.id } + get :index, params: params + expect(assigns(:fandom)).to eq(@fandom) + end + + describe "when the fandom id is invalid" do + it "raises a 404 for an invalid id" do + params = { fandom_id: 0 } + expect { get :index, params: params } + .to raise_error ActiveRecord::RecordNotFound + end + end + + describe "when the fandom id is empty" do + it "returns the work" do + params = { fandom_id: nil } + get :index, params: params + expect(assigns(:works)).to include(@work) + end + end + + describe "without caching" do + before do + AdminSetting.first.update_attribute(:enable_test_caching, false) + end + + it "returns the result with different works the second time" do + get :index + expect(assigns(:works)).to include(@work) + work2 = create(:work) + get :index + expect(assigns(:works)).to include(work2) + end + end + + describe "with caching" do + before do + AdminSetting.first.update_attribute(:enable_test_caching, true) + end + + context "with NO owner tag" do + it "returns the same result the second time when a new work is created within the expiration time" do + get :index + expect(assigns(:works)).to include(@work) + work2 = create(:work) + run_all_indexing_jobs + get :index + expect(assigns(:works)).not_to include(work2) + end + end + + context "with a valid owner tag" do + before do + @fandom2 = create(:canonical_fandom) + @work2 = create(:work, fandom_string: @fandom2.name) + run_all_indexing_jobs + end + + it "only gets works under that tag" do + get :index, params: { tag_id: @fandom.name } + expect(assigns(:works).items).to include(@work) + expect(assigns(:works).items).not_to include(@work2) + end + + it "shows different results on second page" do + get :index, params: { tag_id: @fandom.name, page: 2 } + expect(assigns(:works).items).not_to include(@work) + end + + context "with restricted works" do + before do + @work2 = create(:work, fandom_string: @fandom.name, restricted: true) + run_all_indexing_jobs + end + + it "shows restricted works to guests" do + get :index, params: { tag_id: @fandom.name } + expect(assigns(:works).items).to include(@work) + expect(assigns(:works).items).not_to include(@work2) + end + + end + + context "when tag is a synonym" do + let(:fandom_synonym) { create(:fandom, merger: @fandom) } + + it "redirects to the merger's work index" do + params = { tag_id: fandom_synonym.name } + get :index, params: params + it_redirects_to tag_works_path(@fandom) + end + + context "when collection is specified" do + let(:collection) { create(:collection) } + + it "redirects to the merger's collection works index" do + params = { tag_id: fandom_synonym.name, collection_id: collection.name } + get :index, params: params + it_redirects_to collection_tag_works_path(collection, @fandom) + end + end + end + end + end + + context "with an invalid owner tag" do + it "raises an error" do + params = { tag_id: "nonexistent_tag" } + expect { get :index, params: params }.to raise_error( + ActiveRecord::RecordNotFound, + "Couldn't find tag named 'nonexistent_tag'" + ) + end + end + + context "with an invalid owner user" do + it "raises an error" do + params = { user_id: "nonexistent_user" } + expect { get :index, params: params }.to raise_error( + ActiveRecord::RecordNotFound + ) + end + + context "with an invalid pseud" do + it "raises an error" do + params = { user_id: "nonexistent_user", pseud_id: "nonexistent_pseud" } + expect { get :index, params: params }.to raise_error( + ActiveRecord::RecordNotFound + ) + end + end + end + + context "with a valid owner user" do + let(:user) { create(:user) } + let!(:user_work) { create(:work, authors: [user.default_pseud]) } + let(:pseud) { create(:pseud, user: user) } + let!(:pseud_work) { create(:work, authors: [pseud]) } + + before { run_all_indexing_jobs } + + it "includes only works for that user" do + params = { user_id: user.login } + get :index, params: params + expect(assigns(:works).items).to include(user_work, pseud_work) + expect(assigns(:works).items).not_to include(@work) + end + + context "with a valid pseud" do + it "includes only works for that pseud" do + params = { user_id: user.login, pseud_id: pseud.name } + get :index, params: params + expect(assigns(:works).items).to include(pseud_work) + expect(assigns(:works).items).not_to include(user_work, @work) + end + end + + context "with an invalid pseud" do + it "includes all of that user's works" do + params = { user_id: user.login, pseud_id: "nonexistent_pseud" } + get :index, params: params + expect(assigns(:works).items).to include(user_work, pseud_work) + expect(assigns(:works).items).not_to include(@work) + end + end + end + end + + describe "update" do + let(:update_user) { create(:user) } + let(:update_work) { + work = create(:work, authors: [update_user.default_pseud]) + create(:chapter, work: work) + work + } + + before do + fake_login_known_user(update_user) + end + + it "doesn't allow the user to add a series that they don't own" do + @series = create(:series) + attrs = { series_attributes: { id: @series.id } } + expect { + put :update, params: { id: update_work.id, work: attrs } + }.not_to change { @series.works.all.count } + expect(response).to render_template :edit + expect(assigns[:work].errors.full_messages).to \ + include("You can't add a work to that series.") + end + + it "redirects to the edit page if the work could not be saved" do + allow_any_instance_of(Work).to receive(:save).and_return(false) + update_work.fandom_string = "Testing" + attrs = { title: "New Work Title" } + put :update, params: { id: update_work.id, work: attrs } + expect(response).to render_template :edit + allow_any_instance_of(Work).to receive(:save).and_call_original + end + + it "updates the editor's pseuds for all chapters" do + new_pseud = create(:pseud, user: update_user) + put :update, params: { id: update_work.id, work: { author_attributes: { ids: [new_pseud.id] } } } + expect(update_work.pseuds.reload).to contain_exactly(new_pseud) + update_work.chapters.reload.each do |c| + expect(c.pseuds.reload).to contain_exactly(new_pseud) + end + end + + it "allows the user to invite co-creators" do + co_creator = create(:user) + co_creator.preference.update!(allow_cocreator: true) + put :update, params: { id: update_work.id, work: { author_attributes: { byline: co_creator.login } } } + expect(update_work.pseuds.reload).not_to include(co_creator.default_pseud) + expect(update_work.user_has_creator_invite?(co_creator)).to be_truthy + end + + it "prevents inviting users who have disallowed co-creators" do + no_co_creator = create(:user) + no_co_creator.preference.update!(allow_cocreator: false) + put :update, params: { id: update_work.id, work: { author_attributes: { byline: no_co_creator.login } } } + expect(response).to render_template :edit + expect(assigns[:work].errors.full_messages).to \ + include "Invalid creator: #{no_co_creator.login} does not allow others to invite them to be a co-creator." + expect(update_work.pseuds.reload).not_to include(no_co_creator.default_pseud) + expect(update_work.user_has_creator_invite?(no_co_creator)).to be_falsey + end + + context "when the work has broken dates" do + let(:update_work) { create(:work, authors: [update_user.default_pseud]) } + let(:update_chapter) { update_work.first_chapter } + + let(:attributes) do + { + backdate: "1", + chapter_attributes: { + published_at: "2021-09-01" + } + } + end + + before do + # Work where chapter published_at did not override revised_at, times + # taken from AO3-5392 + update_work.update_column(:revised_at, Time.new(2018, 4, 22, 23, 51, 42, "+04:00")) + update_chapter.update_column(:published_at, Date.new(2015, 7, 23)) + end + + it "can be backdated" do + put :update, params: { id: update_work.id, work: attributes } + + expect(update_chapter.reload.published_at).to eq(Date.new(2021, 9, 1)) + expect(update_work.reload.revised_at).to eq(Time.utc(2021, 9, 1, 12)) # noon UTC + end + end + + # If the time zone in config/application.rb is changed to something other + # than the default (UTC), these tests will need adjusting: + context "when redating to the present" do + let!(:update_work) do + # November 30, 2 PM UTC -- no time zone oddities here + travel_to(Time.utc(2021, 11, 30, 14)) do + create(:work, authors: [update_user.default_pseud]) + end + end + + let(:attributes) do + { + backdate: "1", + chapter_attributes: { + published_at: "2021-12-05" + } + } + end + + before do + travel_to(redate_time) + + # Simulate the system time being UTC: + allow(Time).to receive(:now).and_return(redate_time) + allow(DateTime).to receive(:now).and_return(redate_time) + allow(Date).to receive(:today).and_return(redate_time.to_date) + + put :update, params: { id: update_work.id, work: attributes } + end + + context "before midnight UTC and after midnight Samara" do + # December 5, 3 AM Europe/Samara (UTC+04:00) -- still December 4 in UTC + let(:redate_time) { Time.new(2021, 12, 5, 3, 0, 0, "+04:00") } + + it "prevents setting the publication date to the future" do + expect(response).to render_template :edit + expect(assigns[:work].errors.full_messages).to \ + include("Publication date can't be in the future.") + end + end + + context "before noon UTC" do + # December 5, 6 AM Europe/Samara -- before noon, but after midnight in both time zones + let(:redate_time) { Time.new(2021, 12, 5, 6, 0, 0, "+04:00") } + + it "doesn't set revised_at to the future" do + update_work.reload + expect(update_work.revised_at).to be <= Time.current + end + end + end + + it "errors and redirects to user page when user is banned" do + fake_login_known_user(banned_user) + attrs = { title: "New Work Title" } + put :update, params: { id: banned_users_work.id, work: attrs } + it_redirects_to_simple(user_path(banned_user)) + expect(flash[:error]).to include("Your account has been banned.") + end + end + + describe "collected" do + let(:collection) { create(:collection) } + let(:collected_user) { create(:user) } + + it "returns not found error if user does not exist" do + expect do + get :collected, params: { user_id: "dummyuser" } + end.to raise_error(ActiveRecord::RecordNotFound) + end + + it "returns not found error if no user is set" do + expect do + get :collected + end.to raise_error(ActiveRecord::RecordNotFound) + end + + context "with anonymous works" do + let(:anonymous_collection) { create(:anonymous_collection) } + + let!(:work) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: collection.name) + end + + let!(:anonymous_work) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: anonymous_collection.name) + end + + before { run_all_indexing_jobs } + + it "does not return anonymous works in collections for guests" do + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work) + expect(assigns(:works)).not_to include(anonymous_work) + end + + it "does not return anonymous works in collections for logged-in users" do + fake_login + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work) + expect(assigns(:works)).not_to include(anonymous_work) + end + + it "returns anonymous works in collections for the author" do + fake_login_known_user(collected_user) + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work, anonymous_work) + end + end + + context "with restricted works" do + let(:collected_fandom) { create(:canonical_fandom) } + let(:collected_fandom_2) { create(:canonical_fandom) } + + let!(:unrestricted_work) do + create(:work, + authors: [collected_user.default_pseud], + fandom_string: collected_fandom.name) + end + + let!(:unrestricted_work_in_collection) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: collection.name, + fandom_string: collected_fandom.name) + end + + let!(:unrestricted_work_2_in_collection) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: collection.name, + fandom_string: collected_fandom_2.name) + end + + let!(:restricted_work_in_collection) do + create(:work, + restricted: true, + authors: [collected_user.default_pseud], + collection_names: collection.name, + fandom_string: collected_fandom.name) + end + + before { run_all_indexing_jobs } + + context "as a guest" do + it "renders the collected form" do + get :collected, params: { user_id: collected_user.login } + expect(response).to render_template("collected") + end + + it "returns ONLY unrestricted works in collections" do + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(unrestricted_work_in_collection, unrestricted_work_2_in_collection) + expect(assigns(:works)).not_to include(unrestricted_work, restricted_work_in_collection) + end + + it "returns filtered works when search parameters are provided" do + get :collected, params: { user_id: collected_user.login, work_search: { query: "fandom_ids:#{collected_fandom_2.id}" }} + expect(assigns(:works)).to include(unrestricted_work_2_in_collection) + expect(assigns(:works)).not_to include(unrestricted_work_in_collection) + end + end + + context "with a logged-in user" do + before { fake_login } + + it "returns ONLY works in collections" do + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(unrestricted_work_in_collection, restricted_work_in_collection) + expect(assigns(:works)).not_to include(unrestricted_work) + end + end + end + + context "with unrevealed works" do + let(:unrevealed_collection) { create(:unrevealed_collection) } + + let!(:work) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: collection.name) + end + + let!(:unrevealed_work) do + create(:work, + authors: [collected_user.default_pseud], + collection_names: unrevealed_collection.name) + end + + before { run_all_indexing_jobs } + + it "doesn't return unrevealed works in collections for guests" do + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work) + expect(assigns(:works)).not_to include(unrevealed_work) + end + + it "doesn't return unrevealed works in collections for logged-in users" do + fake_login + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work) + expect(assigns(:works)).not_to include(unrevealed_work) + end + + it "returns unrevealed works in collections for the author" do + fake_login_known_user(collected_user) + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works)).to include(work, unrevealed_work) + end + end + + context "with sorting options" do + let!(:new_work) do + create(:work, + title: "New Title", + authors: [collected_user.default_pseud], + collection_names: collection.name, + created_at: 3.days.ago, + revised_at: 3.days.ago) + end + + let!(:old_work) do + create(:work, + title: "Old Title", + authors: [collected_user.default_pseud], + collection_names: collection.name, + created_at: 30.days.ago, + revised_at: 30.days.ago) + end + + let!(:revised_work) do + create(:work, + title: "Revised Title", + authors: [collected_user.default_pseud], + collection_names: collection.name, + created_at: 20.days.ago, + revised_at: 2.days.ago) + end + + before { run_all_indexing_jobs } + + it "sorts by date" do + get :collected, params: { user_id: collected_user.login } + expect(assigns(:works).map(&:title)).to eq([revised_work, new_work, old_work].map(&:title)) + get :collected, params: { user_id: collected_user.login, work_search: { sort_direction: "asc" } } + expect(assigns(:works).map(&:title)).to eq([old_work, new_work, revised_work].map(&:title)) + end + + it "sorts by title" do + get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on" } } + expect(assigns(:works).map(&:title)).to eq([new_work, old_work, revised_work].map(&:title)) + get :collected, params: { user_id: collected_user.login, work_search: { sort_column: "title_to_sort_on", sort_direction: "desc" } } + expect(assigns(:works).map(&:title)).to eq([revised_work, old_work, new_work].map(&:title)) + end + end + end describe "destroy" do let(:work) { create(:work) } From ec250a0eca998c1ce22969bbcae9e85db8f1d749 Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:58:48 +0000 Subject: [PATCH 3/5] Appease the dog --- app/models/chapter.rb | 16 ++++++++-------- app/models/work.rb | 2 +- .../works/default_rails_actions_spec.rb | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index ed1e6ed4f53..e015fe315b9 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -11,7 +11,7 @@ class Chapter < ApplicationRecord # acts_as_list scope: 'work_id = #{work_id}' acts_as_commentable - has_many :comments, as: :commentable + has_many :comments, as: :commentable, dependent: :nil # Handled in #delete_all_comments validates_length_of :title, allow_blank: true, maximum: ArchiveConfig.TITLE_MAX, too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) @@ -52,7 +52,14 @@ def inherit_creatorships scope :in_order, -> { order(:position) } scope :posted, -> { where(posted: true) } + before_destroy :fix_positions_before_destroy, :invalidate_chapter_count, :delete_all_comments + after_destroy :update_work_stats + after_save :fix_positions + after_save :invalidate_chapter_count, + if: Proc.new { |chapter| chapter.saved_change_to_posted? } + after_commit :update_series_index + def fix_positions if work&.persisted? positions_changed = false @@ -78,12 +85,6 @@ def fix_positions end end - after_save :invalidate_chapter_count, - if: Proc.new { |chapter| chapter.saved_change_to_posted? } - - before_destroy :fix_positions_before_destroy, :invalidate_chapter_count, :delete_all_comments - after_destroy :update_work_stats - def fix_positions_before_destroy if work&.persisted? && position chapters = work.chapters.where(["position > ?", position]) @@ -98,7 +99,6 @@ def delete_all_comments inbox_comments.in_batches.delete_all end - after_commit :update_series_index def update_series_index return unless work&.series.present? && should_reindex_series? work.serial_works.each(&:update_series_index) diff --git a/app/models/work.rb b/app/models/work.rb index 99ee57774a8..72203139a11 100755 --- a/app/models/work.rb +++ b/app/models/work.rb @@ -41,7 +41,7 @@ class Work < ApplicationRecord accepts_nested_attributes_for :challenge_claims acts_as_commentable - has_many :comments, as: :commentable + has_many :comments, as: :commentable, dependent: :nil # Handled in chapters#delete_all_comments has_many :total_comments, class_name: 'Comment', through: :chapters has_many :kudos, as: :commentable, dependent: :delete_all diff --git a/spec/controllers/works/default_rails_actions_spec.rb b/spec/controllers/works/default_rails_actions_spec.rb index 2f48e6c40e3..7b50279a6b0 100644 --- a/spec/controllers/works/default_rails_actions_spec.rb +++ b/spec/controllers/works/default_rails_actions_spec.rb @@ -918,7 +918,7 @@ def call_with_params(params) first_chapter = work.first_chapter second_chapter = create(:chapter, work: work, position: 2) - comment = create(:comment, commentable: first_chapter, parent: first_chapter) + create(:comment, commentable: first_chapter, parent: first_chapter) create(:comment, commentable: second_chapter, parent: second_chapter) fake_login_known_user(work.users.first) end @@ -927,7 +927,8 @@ def call_with_params(params) delete :destroy, params: { id: work.id } it_redirects_to_with_notice(user_works_path(controller.current_user), "Your work #{work_title} was deleted.") - expect { work.reload }.to raise_exception(ActiveRecord::RecordNotFound) + expect { work.reload } + .to raise_exception(ActiveRecord::RecordNotFound) expect(Comment.count).to eq(0) expect(InboxComment.count).to eq(0) end From 809305c594c900b0300eb0be869c08b0f88aa1aa Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:02:07 +0000 Subject: [PATCH 4/5] part 2 --- app/models/chapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index e015fe315b9..4752e5a2b68 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -56,8 +56,7 @@ def inherit_creatorships after_destroy :update_work_stats after_save :fix_positions - after_save :invalidate_chapter_count, - if: Proc.new { |chapter| chapter.saved_change_to_posted? } + after_save :invalidate_chapter_count, if: proc { |chapter| chapter.saved_change_to_posted? } after_commit :update_series_index def fix_positions From c312e02bd206d4ee7e86c12b86f18182a6993b3e Mon Sep 17 00:00:00 2001 From: weeklies <80141759+weeklies@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:07:24 +0000 Subject: [PATCH 5/5] Fix? --- app/models/chapter.rb | 2 +- app/models/work.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/chapter.rb b/app/models/chapter.rb index 4752e5a2b68..ffbd0baea1c 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -11,7 +11,7 @@ class Chapter < ApplicationRecord # acts_as_list scope: 'work_id = #{work_id}' acts_as_commentable - has_many :comments, as: :commentable, dependent: :nil # Handled in #delete_all_comments + has_many :comments, as: :commentable # Handled in #delete_all_comments validates_length_of :title, allow_blank: true, maximum: ArchiveConfig.TITLE_MAX, too_long: ts("must be less than %{max} characters long.", max: ArchiveConfig.TITLE_MAX) diff --git a/app/models/work.rb b/app/models/work.rb index 72203139a11..9abc95754b9 100755 --- a/app/models/work.rb +++ b/app/models/work.rb @@ -41,7 +41,7 @@ class Work < ApplicationRecord accepts_nested_attributes_for :challenge_claims acts_as_commentable - has_many :comments, as: :commentable, dependent: :nil # Handled in chapters#delete_all_comments + has_many :comments, as: :commentable # Handled in chapters#delete_all_comments has_many :total_comments, class_name: 'Comment', through: :chapters has_many :kudos, as: :commentable, dependent: :delete_all