diff --git a/app/controllers/post_replacements_controller.rb b/app/controllers/post_replacements_controller.rb index 73e7d258a..b001b2e8d 100644 --- a/app/controllers/post_replacements_controller.rb +++ b/app/controllers/post_replacements_controller.rb @@ -1,7 +1,7 @@ class PostReplacementsController < ApplicationController - respond_to :html + respond_to :html, :json before_action :moderator_only, only: [:destroy] - before_action :janitor_only, only: [:create, :new, :approve, :reject, :promote] + before_action :janitor_only, only: [:create, :new, :approve, :reject, :promote, :toggle_penalize] content_security_policy only: [:new] do |p| p.img_src :self, :data, "*" end @@ -15,48 +15,57 @@ class PostReplacementsController < ApplicationController def create @post = Post.find(params[:post_id]) @post_replacement = @post.replacements.create(create_params.merge(creator_id: CurrentUser.id, creator_ip_addr: CurrentUser.ip_addr)) - if @post_replacement.errors.size == 0 - flash[:notice] = "Post replacement submitted" - else + if @post_replacement.errors.any? flash[:notice] = @post_replacement.errors.full_messages.join('; ') + else + flash[:notice] = "Post replacement submitted" end respond_with(@post_replacement, location: @post) end def approve @post_replacement = PostReplacement.find(params[:id]) - @post_replacement.approve! + @post_replacement.approve!(penalize_current_uploader: params[:penalize_current_uploader]) respond_with(@post_replacement, location: post_path(@post_replacement.post)) end + def toggle_penalize + @post_replacement = PostReplacement.find(params[:id]) + @post_replacement.toggle_penalize! + + respond_with(@post_replacement) + end + def reject @post_replacement = PostReplacement.find(params[:id]) @post_replacement.reject! - respond_with(@post_replacement) + respond_with(@post_replacement, location: post_path(@post_replacement.post)) end def destroy @post_replacement = PostReplacement.find(params[:id]) @post_replacement.destroy - respond_with(@post_replacement) + respond_with(@post_replacement, location: post_path(@post_replacement.post)) end def promote @post_replacement = PostReplacement.find(params[:id]) - @post = @post_replacement.promote! - if @post.errors.any? - respond_with(@post) + @upload = @post_replacement.promote! + if @post_replacement.errors.any? + respond_with(@post_replacement) + elsif @upload.errors.any? + respond_with(@upload) else - respond_with(@post.post) + respond_with(@upload.post) end end def index params[:search][:post_id] = params.delete(:post_id) if params.has_key?(:post_id) - @post_replacements = PostReplacement.visible(CurrentUser.user).search(search_params).paginate(params[:page], limit: params[:limit]) + @post_replacements = PostReplacement.includes(:post).visible(CurrentUser.user).search(search_params).paginate(params[:page], limit: params[:limit]) respond_with(@post_replacements) end diff --git a/app/helpers/post_replacement_helper.rb b/app/helpers/post_replacement_helper.rb index 1a287c8f7..e8894b3a5 100644 --- a/app/helpers/post_replacement_helper.rb +++ b/app/helpers/post_replacement_helper.rb @@ -1,6 +1,10 @@ module PostReplacementHelper def replacement_thumbnail(replacement) return tag.a(image_tag(replacement.replacement_thumb_url), href: replacement.replacement_file_url) if replacement.file_visible_to?(CurrentUser.user) - image_tag(replacement.replacement_thumb_url) + if replacement.post.deleteblocked? + image_tag(Danbooru.config.deleted_preview_url) + else + image_tag(replacement.replacement_thumb_url) + end end end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index a551f70b2..314d3bc7f 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -37,6 +37,7 @@ export { default as Note } from '../src/javascripts/notes.js'; export { default as Post } from '../src/javascripts/posts.js'; export { default as PostDeletion } from "../src/javascripts/post_delete.js"; export { default as PostModeMenu } from '../src/javascripts/post_mode_menu.js'; +export { default as PostReplacement } from '../src/javascripts/post_replacement.js'; export { default as PostVersions } from '../src/javascripts/post_versions.js'; export { default as RelatedTag } from '../src/javascripts/related_tag.js'; export { default as Shortcuts } from '../src/javascripts/shortcuts.js'; diff --git a/app/javascript/src/javascripts/post_replacement.js b/app/javascript/src/javascripts/post_replacement.js new file mode 100644 index 000000000..3e0c6e15e --- /dev/null +++ b/app/javascript/src/javascripts/post_replacement.js @@ -0,0 +1,82 @@ +import Utility from './utility' + +let PostReplacement = {}; + +PostReplacement.initialize_all = function () { + $(".replacement-approve-action").on("click", e => { + const target = $(e.target); + e.preventDefault(); + PostReplacement.approve(target.data("replacement-id"), target.data("penalize")); + }); + $(".replacement-reject-action").on("click", e => { + e.preventDefault(); + PostReplacement.reject($(e.target).data("replacement-id")); + }); + $(".replacement-promote-action").on("click", e => { + e.preventDefault(); + PostReplacement.promote($(e.target).data("replacement-id")); + }); + $(".replacement-toggle-penalize-action").on("click", e => { + e.preventDefault(); + PostReplacement.toggle_penalize($(e.target).data("replacement-id")); + }); +}; + +PostReplacement.approve = function (id, penalize_current_uploader) { + $.ajax({ + type: "PUT", + url: `/post_replacements/${id}/approve.json`, + data: { + penalize_current_uploader: penalize_current_uploader + }, + dataType: 'json' + }).done(function () { + Utility.notice("Post Replacement accepted"); + }).fail(function (data, status, xhr) { + Utility.error(data.responseText); + }); +}; + +PostReplacement.reject = function (id) { + $.ajax({ + type: "PUT", + url: `/post_replacements/${id}/reject.json`, + dataType: 'json' + }).done(function () { + Utility.notice("Post Replacement rejected"); + }).fail(function (data, status, xhr) { + Utility.error(data.responseText); + }); +} + +PostReplacement.promote = function (id) { + $.ajax({ + type: "POST", + url: `/post_replacements/${id}/promote.json`, + dataType: 'json' + }).done(function (data) { + Utility.notice(`Replacement promoted to post #${data.post.id}`) + }).fail(function (data, status, xhr) { + Utility.error(data.responseText); + }); +} + +PostReplacement.toggle_penalize = function (id) { + $.ajax({ + type: "PUT", + url: `/post_replacements/${id}/toggle_penalize.json`, + dataType: 'json' + }).done(function (data) { + Utility.notice("User upload limit updated"); + }).fail(function (data, status, xhr) { + Utility.error(data.responseText); + }); +} + +$(function () { + if ($("#c-post-replacements").length) + PostReplacement.initialize_all(); +}); + + +export default PostReplacement diff --git a/app/logical/upload_service/replacer.rb b/app/logical/upload_service/replacer.rb index d435e49c8..861a75297 100644 --- a/app/logical/upload_service/replacer.rb +++ b/app/logical/upload_service/replacer.rb @@ -35,7 +35,7 @@ class UploadService raise ProcessingError, "Could not create post file backup?" if !repl.valid? end - def process! + def process!(penalize_current_uploader:) # Prevent trying to replace deleted posts raise ProcessingError, "Cannot replace post: post is deleted." if post.is_deleted? @@ -76,6 +76,8 @@ class UploadService post.generated_samples = nil end + previous_uploader = post.uploader_id + post.md5 = upload.md5 post.file_ext = upload.file_ext post.image_width = upload.image_width @@ -88,10 +90,22 @@ class UploadService post.uploader_ip_addr = replacement.creator_ip_addr post.save! + + # rescaling notes reloads the post, be careful when accessing previous values rescale_notes(post) update_ugoira_frame_data(post, upload) - replacement.update({status: 'approved', approver_id: CurrentUser.id}) + replacement.update({ + status: 'approved', + approver_id: CurrentUser.id, + uploader_id_on_approve: previous_uploader, + penalize_uploader_on_approve: penalize_current_uploader.to_s.truthy? + }) + + UserStatus.for_user(previous_uploader).update_all("own_post_replaced_count = own_post_replaced_count + 1") + if penalize_current_uploader.to_s.truthy? + UserStatus.for_user(previous_uploader).update_all("own_post_replaced_penalize_count = own_post_replaced_penalize_count + 1") + end if post.is_video? post.generate_video_samples(later: true) diff --git a/app/models/post_replacement.rb b/app/models/post_replacement.rb index 4ecb98696..29c7b8ea0 100644 --- a/app/models/post_replacement.rb +++ b/app/models/post_replacement.rb @@ -3,6 +3,7 @@ class PostReplacement < ApplicationRecord belongs_to :post belongs_to :creator, class_name: "User" belongs_to :approver, class_name: "User", optional: true + belongs_to :uploader_on_approve, class_name: "User", foreign_key: :uploader_id_on_approve, optional: true attr_accessor :replacement_file, :replacement_url, :final_source, :tags, :is_backup validate :user_is_not_limited, on: :create @@ -168,33 +169,63 @@ class PostReplacement < ApplicationRecord module ApiMethods def hidden_attributes - super + [:storage_id] + super + %i[storage_id protected uploader_id_on_approve penalize_uploader_on_approve] end end module ProcessingMethods - def approve! + def approve!(penalize_current_uploader:) + unless ["pending", "original"].include? status + errors.add(:status, "must be pending or original to approve") + return + end + transaction do ModAction.log(:post_replacement_accept, {post_id: post.id, replacement_id: self.id, old_md5: post.md5, new_md5: self.md5}) processor = UploadService::Replacer.new(post: post, replacement: self) - processor.process! + processor.process!(penalize_current_uploader: penalize_current_uploader) end post.update_index end + def toggle_penalize! + if status != "approved" + errors.add(:status, "must be approved to penalize") + return + end + + if penalize_uploader_on_approve + UserStatus.for_user(uploader_on_approve).update_all("own_post_replaced_penalize_count = own_post_replaced_penalize_count - 1") + else + UserStatus.for_user(uploader_on_approve).update_all("own_post_replaced_penalize_count = own_post_replaced_penalize_count + 1") + end + update_attribute(:penalize_uploader_on_approve, !penalize_uploader_on_approve) + end + def promote! + if status != "pending" + errors.add(:status, "must be pending to promote") + return + end + transaction do processor = UploadService.new(new_upload_params) - new_post = processor.start! + new_upload = processor.start! update_attribute(:status, 'promoted') - new_post + new_upload end post.update_index end def reject! + if status != "pending" + errors.add(:status, "must be pending to reject") + return + end + ModAction.log(:post_replacement_reject, {post_id: post.id, replacement_id: self.id}) update_attribute(:status, 'rejected') + UserStatus.for_user(creator_id).update_all("post_replacement_rejected_count = post_replacement_rejected_count + 1") post.update_index end end @@ -226,15 +257,23 @@ class PostReplacement < ApplicationRecord q = q.attribute_exact_matches(:status, params[:status]) if params[:creator_id].present? - q = q.where(creator_id: params[:creator_id].split(",").map(&:to_i)) + q = q.where("creator_id in (?)", params[:creator_id].split(",").first(100).map(&:to_i)) end if params[:creator_name].present? - q = q.where(creator_id: User.name_to_id(params[:creator_name])) + q = q.where("creator_id = ?", User.name_to_id(params[:creator_name])) + end + + if params[:uploader_id_on_approve].present? + q = q.where("uploader_id_on_approve in (?)", params[:uploader_id_on_approve].split(",").first(100).map(&:to_i)) + end + + if params[:uploader_name_on_approve].present? + q = q.where("uploader_id_on_approve = ?", User.name_to_id(params[:uploader_name_on_approve])) end if params[:post_id].present? - q = q.where(post_id: params[:post_id].split(",").map(&:to_i)) + q = q.where("post_id in (?)", params[:post_id].split(",").first(100).map(&:to_i)) end @@ -257,6 +296,18 @@ class PostReplacement < ApplicationRecord where(creator_id: id.to_i) end + def for_uploader_on_approve(id) + where(uploader_id_on_approve: id.to_i) + end + + def penalized + where(penalize_uploader_on_approve: true) + end + + def not_penalized + where(penalize_uploader_on_approve: false) + end + def visible(user) return where('status != ?', 'rejected') if user.is_anonymous? return all if user.is_janitor? diff --git a/app/models/user.rb b/app/models/user.rb index 22967487b..bd893d5a3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -584,7 +584,8 @@ class User < ApplicationRecord def upload_limit_pieces deleted_count = Post.deleted.for_user(id).count - rejected_replacement_count = PostReplacement.rejected.for_user(id).count + rejected_replacement_count = post_replacement_rejected_count + replaced_penalize_count = own_post_replaced_penalize_count unapproved_count = Post.pending_or_flagged.for_user(id).count unapproved_replacements_count = PostReplacement.pending.for_user(id).count approved_count = Post.for_user(id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count @@ -758,6 +759,18 @@ class User < ApplicationRecord feedback.negative.count end + def post_replacement_rejected_count + user_status.post_replacement_rejected_count + end + + def own_post_replaced_count + user_status.own_post_replaced_count + end + + def own_post_replaced_penalize_count + user_status.own_post_replaced_penalize_count + end + def refresh_counts! self.class.without_timeout do UserStatus.where(user_id: id).update_all( diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb index afe402dc5..10b0e4cdb 100644 --- a/app/presenters/user_presenter.rb +++ b/app/presenters/user_presenter.rb @@ -78,6 +78,10 @@ class UserPresenter template.link_to(user.post_deleted_count, template.deleted_posts_path(user_id: user.id)) end + def replaced_upload_count(template) + template.link_to(user.own_post_replaced_count, template.post_replacements_path(search: {uploader_name_on_approve: user.name})) + end + def favorite_count(template) template.link_to(user.favorite_count, template.favorites_path(:user_id => user.id)) end diff --git a/app/views/post_replacements/_search.html.erb b/app/views/post_replacements/_search.html.erb index c488e3157..57d21666f 100644 --- a/app/views/post_replacements/_search.html.erb +++ b/app/views/post_replacements/_search.html.erb @@ -2,6 +2,7 @@ <%= f.input :md5, label: "MD5", input_html: { value: params.dig(:search, :md5) } %> <%= f.input :creator_name, label: "Creator", input_html: { value: params.dig(:search, :creator_name) } %> <%= f.input :post_id, label: "Post ID", input_html: { value: params.dig(:search, :post_id) } %> + <%= f.input :uploader_name_on_approve, label: "Uploader on Approve", input_html: { value: params.dig(:search, :uploader_name_on_approve) } %> <%= f.input :status, label: "status", collection: ["pending", "rejected", "approved", "promoted"], include_blank: true, selected: params.dig(:search, :status) %> <%= f.submit "Search" %> <% end %> diff --git a/app/views/post_replacements/index.html.erb b/app/views/post_replacements/index.html.erb index 38a7bd663..64257480b 100644 --- a/app/views/post_replacements/index.html.erb +++ b/app/views/post_replacements/index.html.erb @@ -70,6 +70,14 @@
Reason
<%= post_replacement.reason %>
<% if post_replacement.status == 'approved' %> +
Original Uploader
+
+ <%= link_to_user post_replacement.uploader_on_approve %> + <% if CurrentUser.is_janitor? %> + | penalized: <%= post_replacement.penalize_uploader_on_approve ? "yes" : "no" %> + <%= link_to "toggle", "#toggle", class: "replacement-toggle-penalize-action", data: { replacement_id: post_replacement.id} %>
+ <% end %> +
Approver
<%= link_to_user post_replacement.approver %>
<% end %> @@ -84,9 +92,15 @@ <% if CurrentUser.is_janitor? %> - <%= link_to "Approve", approve_post_replacement_path(post_replacement), method: :PUT %>
- <%= link_to "Reject", reject_post_replacement_path(post_replacement), method: :PUT %>
- <%= link_to "As New Post", promote_post_replacement_path(post_replacement), method: :PUT %>
+ <% if post_replacement.status == "pending"%> + <%= link_to "Approve And Penalize Current Uploader", "#approve", class: "replacement-approve-action", data: { replacement_id: post_replacement.id, penalize: true} %>
+ <%= link_to "Approve", "#approve", class: "replacement-approve-action", data: { replacement_id: post_replacement.id, penalize: false} %>
+ <%= link_to "Reject", "#reject", class: "replacement-reject-action", data: { replacement_id: post_replacement.id} %>
+ <%= link_to "As New Post", "#promote", class: "replacement-promote-action", data: { replacement_id: post_replacement.id} %>
+ <% end %> + <% if post_replacement.status == "original" %> + <%= link_to "Reset To", "#approve", class: "replacement-approve-action", data: { replacement_id: post_replacement.id, penalize: false} %>
+ <% end %> <% end %> <% if CurrentUser.is_moderator? %> <%= link_to "Destroy", post_replacement_path(post_replacement), method: :DELETE, 'data-confirm': 'Are you sure you want to destroy this replacement?' %> diff --git a/app/views/users/_statistics.html.erb b/app/views/users/_statistics.html.erb index 920ccbaf8..979b0e18b 100644 --- a/app/views/users/_statistics.html.erb +++ b/app/views/users/_statistics.html.erb @@ -57,12 +57,13 @@ - Deleted Posts + Deleted/Replaced Posts <%= presenter.deleted_upload_count(self) %> <% if CurrentUser.is_moderator? %> [<%= link_to "sample", posts_path(:tags => "user:#{user.name} order:random limit:300 status:deleted") %>] <% end %> + / <%= presenter.replaced_upload_count(self) %> Post Changes diff --git a/config/routes.rb b/config/routes.rb index a2dd8bed7..f6728c481 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -250,7 +250,8 @@ Rails.application.routes.draw do member do put :approve put :reject - put :promote + post :promote + put :toggle_penalize end end resources :deleted_posts, only: [:index] diff --git a/db/migrate/20210625155528_add_replacement_audit_stats.rb b/db/migrate/20210625155528_add_replacement_audit_stats.rb new file mode 100644 index 000000000..e7a161a96 --- /dev/null +++ b/db/migrate/20210625155528_add_replacement_audit_stats.rb @@ -0,0 +1,9 @@ +class AddReplacementAuditStats < ActiveRecord::Migration[6.1] + def change + add_column :post_replacements2, :uploader_id_on_approve, :int + add_column :post_replacements2, :penalize_uploader_on_approve, :boolean + add_column :user_statuses, :own_post_replaced_count, :int, nil: false, default: 0 + add_column :user_statuses, :own_post_replaced_penalize_count, :int, nil: false, default: 0 + add_column :user_statuses, :post_replacement_rejected_count, :int, nil: false, default: 0 + end +end diff --git a/db/structure.sql b/db/structure.sql index 261914176..dad12bd03 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1638,7 +1638,9 @@ CREATE TABLE public.post_replacements2 ( storage_id character varying NOT NULL, status character varying DEFAULT 'pending'::character varying NOT NULL, reason character varying NOT NULL, - protected boolean DEFAULT false NOT NULL + protected boolean DEFAULT false NOT NULL, + uploader_id_on_approve integer, + penalize_uploader_on_approve boolean ); @@ -2588,7 +2590,10 @@ CREATE TABLE public.user_statuses ( pool_edit_count integer DEFAULT 0 NOT NULL, blip_count integer DEFAULT 0 NOT NULL, set_count integer DEFAULT 0 NOT NULL, - artist_edit_count integer DEFAULT 0 NOT NULL + artist_edit_count integer DEFAULT 0 NOT NULL, + own_post_replaced_count integer DEFAULT 0, + own_post_replaced_penalize_count integer DEFAULT 0, + post_replacement_rejected_count integer DEFAULT 0 ); @@ -5263,6 +5268,6 @@ INSERT INTO "schema_migrations" (version) VALUES ('20210426025625'), ('20210430201028'), ('20210506235640'), -('20210718172512'); - +('20210718172512'), +('20210625155528'); diff --git a/script/fixes/103_fill_new_post_replacement_fields.rb b/script/fixes/103_fill_new_post_replacement_fields.rb new file mode 100644 index 000000000..66b5f21a9 --- /dev/null +++ b/script/fixes/103_fill_new_post_replacement_fields.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'config', 'environment')) + +UserStatus.find_each do |user_status| + user_status.update_column(:post_replacement_rejected_count, PostReplacement.rejected.for_user(user_status.user.id).count) +end + +# post ids who have two replacements, one approved, one original +PostReplacement.select(:post_id).where(status: ["approved", "original"]).group(:post_id) + .having("count(post_id) = 2").map(&:post_id).each do |post_id| + replacements = PostReplacement.where(post_id: post_id).order(:status) + + approved = replacements[0] + original = replacements[1] + + approved.uploader_id_on_approve = original.creator_id + approved.penalize_uploader_on_approve = true + approved.save! + + user_status = UserStatus.for_user(original.creator_id) + user_status.update_all("own_post_replaced_count = own_post_replaced_count + 1") + user_status.update_all("own_post_replaced_penalize_count = own_post_replaced_penalize_count + 1") +end diff --git a/test/functional/post_replacements_controller_test.rb b/test/functional/post_replacements_controller_test.rb index bacd5fd4a..4486d3967 100644 --- a/test/functional/post_replacements_controller_test.rb +++ b/test/functional/post_replacements_controller_test.rb @@ -3,11 +3,11 @@ require 'test_helper' class PostReplacementsControllerTest < ActionDispatch::IntegrationTest setup do - Sidekiq::Testing::inline! + Sidekiq::Testing.inline! end teardown do - Sidekiq::Testing::fake! + Sidekiq::Testing.fake! end context "The post replacements controller" do @@ -43,7 +43,7 @@ class PostReplacementsControllerTest < ActionDispatch::IntegrationTest context "reject action" do should "reject replacement" do put_auth reject_post_replacement_path(@replacement), @user - assert_redirected_to post_replacement_path(@replacement) + assert_redirected_to post_path(@post) @replacement.reload @post.reload assert_equal @replacement.status, "rejected" @@ -64,7 +64,7 @@ class PostReplacementsControllerTest < ActionDispatch::IntegrationTest context "promote action" do should "create post" do - put_auth promote_post_replacement_path(@replacement), @user + post_auth promote_post_replacement_path(@replacement), @user last_post = Post.last assert_redirected_to post_path(last_post) @replacement.reload @@ -74,6 +74,18 @@ class PostReplacementsControllerTest < ActionDispatch::IntegrationTest end end + context "toggle action" do + should "change penalize_uploader flag" do + put_auth approve_post_replacement_path(@replacement, penalize_current_uploader: true), @user + @replacement.reload + assert @replacement.penalize_uploader_on_approve + put_auth toggle_penalize_post_replacement_path(@replacement), @user + assert_redirected_to post_replacement_path(@replacement) + @replacement.reload + assert !@replacement.penalize_uploader_on_approve + end + end + context "index action" do should "render" do get post_replacements_path diff --git a/test/unit/post_replacement_test.rb b/test/unit/post_replacement_test.rb index 77f6771e3..603895655 100644 --- a/test/unit/post_replacement_test.rb +++ b/test/unit/post_replacement_test.rb @@ -77,7 +77,7 @@ class PostReplacementTest < ActiveSupport::TestCase end should "affect user upload limit" do - assert_difference("PostReplacement.pending.for_user(@user.id).count", 1) do + assert_difference(->{PostReplacement.pending.for_user(@user.id).count}, 1) do @replacement = @post.replacements.create(FactoryBot.attributes_for(:png_replacement).merge(creator: @user, creator_ip_addr: '127.0.0.1')) end end @@ -109,10 +109,26 @@ class PostReplacementTest < ActiveSupport::TestCase end should "give user back their upload slot" do - assert_difference("PostReplacement.pending.for_user(@user.id).count", -1) do + assert_difference(->{PostReplacement.pending.for_user(@user.id).count}, -1) do @replacement.reject! end end + + should "increment the users rejected replacements count" do + assert_difference(->{@user.post_replacement_rejected_count}, 1) do + assert_difference(->{PostReplacement.rejected.for_user(@user.id).count}, 1) do + @replacement.reject! + @user.reload + end + end + end + + should "work only once for pending replacements" do + @replacement.reject! + assert_equal [], @replacement.errors.full_messages + @replacement.reject! + assert_equal ["Status must be pending to reject"], @replacement.errors.full_messages + end end context "Approve:" do @@ -124,20 +140,21 @@ class PostReplacementTest < ActiveSupport::TestCase should "create a mod action" do assert_difference("ModAction.count") do - @replacement.approve! + @replacement.approve! penalize_current_uploader: true end end should "fail if post cannot be backed up" do @post.md5 = "123" # Breaks file path, should force backup to fail. assert_raise(ProcessingError) do - @replacement.approve! + @replacement.approve! penalize_current_uploader: true end end + should "update post with new image" do old_md5 = @post.md5 - @replacement.approve! + @replacement.approve! penalize_current_uploader: true @post.reload assert_not_equal @post.md5, old_md5 assert_equal @replacement.image_width, @post.image_width @@ -151,25 +168,25 @@ class PostReplacementTest < ActiveSupport::TestCase should "generate videos samples if replacement is video" do @replacement = FactoryBot.create(:webm_replacement, creator: @user, creator_ip_addr: '127.0.0.1', post: @post) @post.expects(:generate_video_samples).times(1) - @replacement.approve! + @replacement.approve! penalize_current_uploader: true end should "delete original files immediately" do sm = Danbooru.config.storage_manager old_md5 = @post.md5 old_ext = @post.file_ext - @replacement.approve! + @replacement.approve! penalize_current_uploader: true @post.reload - assert_not File.exists?(sm.file_path(old_md5, old_ext, :original)) - assert_not File.exists?(sm.file_path(old_md5, old_ext, :preview)) - assert_not File.exists?(sm.file_path(old_md5, old_ext, :large)) - assert_not File.exists?(sm.file_path(old_md5, old_ext, :original, protected=true)) + assert_not File.exist?(sm.file_path(old_md5, old_ext, :original)) + assert_not File.exist?(sm.file_path(old_md5, old_ext, :preview)) + assert_not File.exist?(sm.file_path(old_md5, old_ext, :large)) + assert_not File.exist?(sm.file_path(old_md5, old_ext, :original, protected=true)) end should "not be able to approve on deleted post" do @post.update_column(:is_deleted, true) assert_raise ProcessingError do - @replacement.approve! + @replacement.approve! penalize_current_uploader: true end end @@ -177,7 +194,7 @@ class PostReplacementTest < ActiveSupport::TestCase old_md5 = @post.md5 old_source = @post.source assert_difference("@post.replacements.size", 1) do - @replacement.approve! + @replacement.approve! penalize_current_uploader: true end new_replacement = @post.replacements.last assert_equal 'original', new_replacement.status @@ -187,21 +204,78 @@ class PostReplacementTest < ActiveSupport::TestCase end should "update users upload counts" do - assert_difference("Post.for_user(@mod_user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count", -1) do - assert_difference("Post.for_user(@user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count", 1) do - @replacement.approve! + assert_difference(->{Post.for_user(@mod_user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count}, -1) do + assert_difference(->{Post.for_user(@user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count}, 1) do + @replacement.approve! penalize_current_uploader: true + end + end + end + + should "update the original users upload limit if penalized" do + assert_difference(->{@mod_user.own_post_replaced_count}, 1) do + assert_difference(->{@mod_user.own_post_replaced_penalize_count}, 1) do + assert_difference(->{PostReplacement.penalized.for_uploader_on_approve(@mod_user.id).count}, 1) do + @replacement.approve! penalize_current_uploader: true + @mod_user.reload + end + end + end + end + + should "not update the original users upload limit if not penalizing" do + assert_difference(-> {@mod_user.own_post_replaced_count}, 1) do + assert_difference(->{@mod_user.own_post_replaced_penalize_count}, 0) do + assert_difference(->{PostReplacement.not_penalized.for_uploader_on_approve(@mod_user.id).count}, 1) do + @replacement.approve! penalize_current_uploader: false + @mod_user.reload + end end end end should "correctly resize the posts notes" do - @replacement.approve! + @replacement.approve! penalize_current_uploader: true @note.reload assert_equal 153, @note.x assert_equal 611, @note.y assert_equal 153, @note.width assert_equal 152, @note.height end + + should "only work on pending or original replacements" do + @replacement.reject! + @replacement.approve! penalize_current_uploader: false + assert_equal(["Status must be pending or original to approve"], @replacement.errors.full_messages) + end + + should "only work once" do + @replacement.approve! penalize_current_uploader: false + assert_equal [], @replacement.errors.full_messages + @replacement.approve! penalize_current_uploader: false + assert_equal ["Status must be pending or original to approve"], @replacement.errors.full_messages + end + end + + context "Toggle:" do + setup do + @replacement = FactoryBot.create(:png_replacement, creator: @user, creator_ip_addr: '127.0.0.1', post: @post) + assert @replacement + end + + should "change the users upload limit" do + @replacement.approve! penalize_current_uploader: false + assert_difference(->{@mod_user.own_post_replaced_penalize_count}, 1) do + assert_difference(->{PostReplacement.penalized.for_uploader_on_approve(@mod_user.id).count}, 1) do + @replacement.toggle_penalize! + @mod_user.reload + end + end + end + + should "only work on appoved replacements" do + @replacement.toggle_penalize! + assert_equal(["Status must be approved to penalize"], @replacement.errors.full_messages) + end end context "Promote:" do @@ -226,8 +300,8 @@ class PostReplacementTest < ActiveSupport::TestCase end should "credit replacer with new post" do - assert_difference("Post.for_user(@mod_user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count", 0) do - assert_difference("Post.for_user(@user.id).where('is_flagged = false AND is_deleted = false').count", 1) do + assert_difference(->{Post.for_user(@mod_user.id).where('is_flagged = false AND is_deleted = false AND is_pending = false').count}, 0) do + assert_difference(->{Post.for_user(@user.id).where('is_flagged = false AND is_deleted = false').count}, 1) do post = @replacement.promote! assert post assert_equal [], post.errors.full_messages @@ -235,5 +309,11 @@ class PostReplacementTest < ActiveSupport::TestCase end end end + + should "only work on pending replacements" do + @replacement.approve! penalize_current_uploader: false + @replacement.promote! + assert_equal(["Status must be pending to promote"], @replacement.errors.full_messages) + end end end