require 'test_helper' require 'helpers/pool_archive_test_helper' require 'helpers/saved_search_test_helper' require 'helpers/iqdb_test_helper' class PostTest < ActiveSupport::TestCase include PoolArchiveTestHelper include SavedSearchTestHelper include IqdbTestHelper def assert_tag_match(posts, query) assert_equal(posts.map(&:id), Post.tag_match(query).pluck(:id)) end def setup super Timecop.travel(2.weeks.ago) do @user = FactoryGirl.create(:user) end CurrentUser.user = @user CurrentUser.ip_addr = "127.0.0.1" mock_saved_search_service! end def teardown super CurrentUser.user = nil CurrentUser.ip_addr = nil end context "Deletion:" do context "Expunging a post" do setup do @upload = FactoryGirl.create(:jpg_upload) @upload.process! @post = @upload.post end should "delete the files" do assert_equal(true, File.exists?(@post.preview_file_path)) assert_equal(true, File.exists?(@post.large_file_path)) assert_equal(true, File.exists?(@post.file_path)) TestAfterCommit.with_commits(true) do @post.expunge! end assert_equal(false, File.exists?(@post.preview_file_path)) assert_equal(false, File.exists?(@post.large_file_path)) assert_equal(false, File.exists?(@post.file_path)) end should "remove the post from iqdb" do mock_iqdb_service! Post.iqdb_sqs_service.expects(:send_message).with("remove\n#{@post.id}") TestAfterCommit.with_commits(true) do @post.expunge! end end context "that is status locked" do setup do @post.update_attributes({:is_status_locked => true}, :as => :admin) end should "not destroy the record" do @post.expunge! assert_equal(1, Post.where("id = ?", @post.id).count) end end context "that belongs to a pool" do setup do SqsService.any_instance.stubs(:send_message) @pool = FactoryGirl.create(:pool) @pool.add!(@post) @post.reload @post.expunge! end should "remove the post from all pools" do @pool.reload assert_equal("", @pool.post_ids) end should "destroy the record" do assert_equal([], @post.errors.full_messages) assert_equal(0, Post.where("id = ?", @post.id).count) end end end context "Deleting a post" do setup do Danbooru.config.stubs(:blank_tag_search_fast_count).returns(nil) end context "that is status locked" do setup do @post = FactoryGirl.create(:post) @post.update_attributes({:is_status_locked => true}, :as => :admin) end should "fail" do @post.delete!("test") assert_equal(["Is status locked ; cannot delete post"], @post.errors.full_messages) assert_equal(1, Post.where("id = ?", @post.id).count) end end context "that is pending" do setup do @post = FactoryGirl.create(:post, is_pending: true) end should "succeed" do @post.delete!("test") assert_equal(true, @post.is_deleted) assert_equal(1, @post.flags.size) assert_match(/test/, @post.flags.last.reason) end end context "with the banned_artist tag" do should "also ban the post" do post = FactoryGirl.create(:post, :tag_string => "banned_artist") post.delete!("test") post.reload assert(post.is_banned?) end end context "that is still in cooldown after being flagged" do should "succeed" do post = FactoryGirl.create(:post) post.flag!("test flag") post.delete!("test deletion") assert_equal(true, post.is_deleted) assert_equal(2, post.flags.size) end end should "update the fast count" do Danbooru.config.stubs(:estimate_post_counts).returns(false) post = FactoryGirl.create(:post, :tag_string => "aaa") assert_equal(1, Post.fast_count) assert_equal(1, Post.fast_count("aaa")) post.delete!("test") assert_equal(1, Post.fast_count) assert_equal(1, Post.fast_count("aaa")) end should "toggle the is_deleted flag" do post = FactoryGirl.create(:post) assert_equal(false, post.is_deleted?) post.delete!("test") assert_equal(true, post.is_deleted?) end should "not decrement the tag counts" do post = FactoryGirl.create(:post, :tag_string => "aaa") assert_equal(1, Tag.find_by_name("aaa").post_count) post.delete!("test") assert_equal(1, Tag.find_by_name("aaa").post_count) end end end context "Parenting:" do context "Assigning a parent to a post" do should "update the has_children flag on the parent" do p1 = FactoryGirl.create(:post) assert(!p1.has_children?, "Parent should not have any children") c1 = FactoryGirl.create(:post, :parent_id => p1.id) p1.reload assert(p1.has_children?, "Parent not updated after child was added") end should "update the has_children flag on the old parent" do p1 = FactoryGirl.create(:post) p2 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.parent_id = p2.id c1.save p1.reload p2.reload assert(!p1.has_children?, "Old parent should not have a child") assert(p2.has_children?, "New parent should have a child") end end context "Expunging a post with" do context "a parent" do should "reset the has_children flag of the parent" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.expunge! p1.reload assert_equal(false, p1.has_children?) end should "reassign favorites to the parent" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) user = FactoryGirl.create(:user) c1.add_favorite!(user) c1.expunge! p1.reload assert(!Favorite.exists?(:post_id => c1.id, :user_id => user.id)) assert(Favorite.exists?(:post_id => p1.id, :user_id => user.id)) assert_equal(0, c1.score) end should "update the parent's has_children flag" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.expunge! p1.reload assert(!p1.has_children?, "Parent should not have children") end end context "one child" do should "remove the parent of that child" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) p1.expunge! c1.reload assert_nil(c1.parent) end end context "two or more children" do should "reparent all children to the first child" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c2 = FactoryGirl.create(:post, :parent_id => p1.id) c3 = FactoryGirl.create(:post, :parent_id => p1.id) p1.expunge! c1.reload c2.reload c3.reload assert_nil(c1.parent_id) assert_equal(c1.id, c2.parent_id) assert_equal(c1.id, c3.parent_id) end end end context "Deleting a post with" do context "a parent" do should "not reassign favorites to the parent by default" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) user = FactoryGirl.create(:gold_user) c1.add_favorite!(user) c1.delete!("test") p1.reload assert(Favorite.exists?(:post_id => c1.id, :user_id => user.id)) assert(!Favorite.exists?(:post_id => p1.id, :user_id => user.id)) end should "reassign favorites to the parent if specified" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) user = FactoryGirl.create(:gold_user) c1.add_favorite!(user) c1.delete!("test", :move_favorites => true) p1.reload assert(!Favorite.exists?(:post_id => c1.id, :user_id => user.id), "Child should not still have favorites") assert(Favorite.exists?(:post_id => p1.id, :user_id => user.id), "Parent should have favorites") end should "not update the parent's has_children flag" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.delete!("test") p1.reload assert(p1.has_children?, "Parent should have children") end end context "one child" do should "not remove the has_children flag" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) p1.delete!("test") p1.reload assert_equal(true, p1.has_children?) end should "not remove the parent of that child" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) p1.delete!("test") c1.reload assert_not_nil(c1.parent) end end context "two or more children" do should "not reparent all children to the first child" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c2 = FactoryGirl.create(:post, :parent_id => p1.id) c3 = FactoryGirl.create(:post, :parent_id => p1.id) p1.delete!("test") c1.reload c2.reload c3.reload assert_equal(p1.id, c1.parent_id) assert_equal(p1.id, c2.parent_id) assert_equal(p1.id, c3.parent_id) end end end context "Undeleting a post with a parent" do should "update with a new approver" do new_user = FactoryGirl.create(:moderator_user) p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.delete!("test") CurrentUser.scoped(new_user, "127.0.0.1") do c1.undelete! end p1.reload assert_equal(new_user.id, c1.approver_id) end should "preserve the parent's has_children flag" do p1 = FactoryGirl.create(:post) c1 = FactoryGirl.create(:post, :parent_id => p1.id) c1.delete!("test") c1.undelete! p1.reload assert_not_nil(c1.parent_id) assert(p1.has_children?, "Parent should have children") end end end context "Moderation:" do context "A deleted post" do setup do @post = FactoryGirl.create(:post, :is_deleted => true) end context "that is status locked" do setup do @post.update_attributes({:is_status_locked => true}, :as => :admin) end should "not allow undeletion" do @post.undelete! assert_equal(["Is status locked ; cannot undelete post"], @post.errors.full_messages) assert_equal(true, @post.is_deleted?) end end context "that is undeleted" do setup do @mod = FactoryGirl.create(:moderator_user) CurrentUser.user = @mod end context "by the approver" do setup do @post.update_attribute(:approver_id, @mod.id) end should "not be permitted" do assert_raises(::Post::ApprovalError) do @post.undelete! end end end context "by the uploader" do setup do @post.update_attribute(:uploader_id, @mod.id) end should "not be permitted" do assert_raises(::Post::ApprovalError) do @post.undelete! end end end end context "when undeleted" do should "be undeleted" do @post.undelete! assert_equal(false, @post.reload.is_deleted?) end should "create a mod action" do @post.undelete! assert_equal("undeleted post ##{@post.id}", ModAction.last.description) end end context "when approved" do should "be undeleted" do @post.approve! assert_equal(false, @post.reload.is_deleted?) end should "create a mod action" do @post.approve! assert_equal("undeleted post ##{@post.id}", ModAction.last.description) end end should "be appealed" do assert_difference("PostAppeal.count", 1) do @post.appeal!("xxx") end assert(@post.is_deleted?, "Post should still be deleted") assert_equal(1, @post.appeals.count) end end context "An approved post" do should "be flagged" do post = FactoryGirl.create(:post) assert_difference("PostFlag.count", 1) do post.flag!("bad") end assert(post.is_flagged?, "Post should be flagged.") assert_equal(1, post.flags.count) end should "not be flagged if no reason is given" do post = FactoryGirl.create(:post) assert_difference("PostFlag.count", 0) do assert_raises(PostFlag::Error) do post.flag!("") end end end end context "An unapproved post" do should "preserve the approver's identity when approved" do post = FactoryGirl.create(:post, :is_pending => true) post.approve! assert_equal(post.approver_id, CurrentUser.id) end context "that was uploaded by person X" do setup do @post = FactoryGirl.create(:post) @post.flag!("reason") end should "not allow person X to approve that post" do approval = @post.approve!(@post.uploader) assert(@post.invalid?) assert_includes(approval.errors.full_messages, "You cannot approve a post you uploaded") end end context "that was previously approved by person X" do setup do @user = FactoryGirl.create(:moderator_user, :name => "xxx") @user2 = FactoryGirl.create(:moderator_user, :name => "yyy") @post = FactoryGirl.create(:post, :approver_id => @user.id) @post.flag!("bad") end should "not allow person X to reapprove that post" do approval = @post.approve!(@user) assert_includes(approval.errors.full_messages, "You have previously approved this post and cannot approve it again") end should "allow person Y to approve the post" do @post.approve!(@user2) assert(@post.valid?) end end context "that has been reapproved" do should "no longer be flagged or pending" do post = FactoryGirl.create(:post) post.flag!("bad") post.approve! assert(post.errors.empty?, post.errors.full_messages.join(", ")) post.reload assert_equal(false, post.is_flagged?) assert_equal(false, post.is_pending?) end end end context "A status locked post" do setup do @post = FactoryGirl.create(:post) @post.update_attributes({:is_status_locked => true}, :as => :admin) end should "not allow new flags" do assert_raises(PostFlag::Error) do @post.flag!("wrong") end end should "not allow new appeals" do assert_raises(PostAppeal::Error) do @post.appeal!("wrong") end end should "not allow approval" do approval = @post.approve! assert_includes(approval.errors.full_messages, "Post is locked and cannot be approved") end end end context "Tagging:" do context "A post" do setup do @post = FactoryGirl.create(:post) end context "as a new user" do setup do @post.update(:tag_string => "aaa bbb ccc ddd tagme") CurrentUser.user = FactoryGirl.create(:user) end should "not allow you to remove tags" do @post.update_attributes(:tag_string => "aaa") assert_equal(["You must have an account at least 1 week old to remove tags"], @post.errors.full_messages) end should "allow you to remove request tags" do @post.update_attributes(:tag_string => "aaa bbb ccc ddd") @post.reload assert_equal("aaa bbb ccc ddd", @post.tag_string) end end context "with a banned artist" do setup do CurrentUser.scoped(FactoryGirl.create(:admin_user)) do @artist = FactoryGirl.create(:artist) @artist.ban! end @post = FactoryGirl.create(:post, :tag_string => @artist.name) end should "ban the post" do assert_equal(true, @post.is_banned?) end end context "with an artist tag that is then changed to copyright" do setup do CurrentUser.user = FactoryGirl.create(:builder_user) @post = Post.find(@post.id) @post.update(:tag_string => "art:abc") @post = Post.find(@post.id) @post.update(:tag_string => "copy:abc") @post.reload end should "update the category of the tag" do assert_equal(Tag.categories.copyright, Tag.find_by_name("abc").category) end should "update the category cache of the tag" do assert_equal(Tag.categories.copyright, Cache.get("tc:abc")) end should "update the tag counts of the posts" do assert_equal(0, @post.tag_count_artist) assert_equal(1, @post.tag_count_copyright) assert_equal(0, @post.tag_count_general) end end context "using a tag prefix on an aliased tag" do setup do FactoryGirl.create(:tag_alias, :antecedent_name => "abc", :consequent_name => "xyz") @post = Post.find(@post.id) @post.update(:tag_string => "art:abc") @post.reload end should "convert the tag to its normalized version" do assert_equal("xyz", @post.tag_string) end end context "tagged with a valid tag" do subject { @post } should allow_value("touhou 100%").for(:tag_string) should allow_value("touhou FOO").for(:tag_string) should allow_value("touhou -foo").for(:tag_string) should allow_value("touhou pool:foo").for(:tag_string) should allow_value("touhou -pool:foo").for(:tag_string) should allow_value("touhou newpool:foo").for(:tag_string) should allow_value("touhou fav:self").for(:tag_string) should allow_value("touhou -fav:self").for(:tag_string) should allow_value("touhou upvote:self").for(:tag_string) should allow_value("touhou downvote:self").for(:tag_string) should allow_value("touhou parent:1").for(:tag_string) should allow_value("touhou child:1").for(:tag_string) should allow_value("touhou source:foo").for(:tag_string) should allow_value("touhou rating:z").for(:tag_string) should allow_value("touhou locked:rating").for(:tag_string) should allow_value("touhou -locked:rating").for(:tag_string) # \u3000 = ideographic space, \u00A0 = no-break space should allow_value("touhou\u3000foo").for(:tag_string) should allow_value("touhou\u00A0foo").for(:tag_string) end context "tagged with an invalid tag" do subject { @post } context "that doesn't already exist" do should_not allow_value("touhou user:evazion").for(:tag_string) should_not allow_value("touhou *~foo").for(:tag_string) should_not allow_value("touhou *-foo").for(:tag_string) should_not allow_value("touhou ,-foo").for(:tag_string) should_not allow_value("touhou ___").for(:tag_string) should_not allow_value("touhou ~foo").for(:tag_string) should_not allow_value("touhou _foo").for(:tag_string) should_not allow_value("touhou foo_").for(:tag_string) should_not allow_value("touhou foo__bar").for(:tag_string) should_not allow_value("touhou foo*bar").for(:tag_string) should_not allow_value("touhou foo,bar").for(:tag_string) should_not allow_value("touhou foo\abar").for(:tag_string) should_not allow_value("touhou café").for(:tag_string) should_not allow_value("touhou 東方").for(:tag_string) end context "that already exists" do setup do %W(___ ~foo _foo foo_ foo__bar foo*bar foo,bar foo\abar café 東方).each do |tag| FactoryGirl.build(:tag, name: tag).save(validate: false) end end should allow_value("touhou ___").for(:tag_string) should allow_value("touhou ~foo").for(:tag_string) should allow_value("touhou _foo").for(:tag_string) should allow_value("touhou foo_").for(:tag_string) should allow_value("touhou foo__bar").for(:tag_string) should allow_value("touhou foo*bar").for(:tag_string) should allow_value("touhou foo,bar").for(:tag_string) should allow_value("touhou foo\abar").for(:tag_string) should allow_value("touhou café").for(:tag_string) should allow_value("touhou 東方").for(:tag_string) end end context "tagged with a metatag" do context "for a parent" do setup do @parent = FactoryGirl.create(:post) end should "update the parent relationships for both posts" do @post.update_attributes(:tag_string => "aaa parent:#{@parent.id}") @post.reload @parent.reload assert_equal(@parent.id, @post.parent_id) assert(@parent.has_children?) end should "not allow self-parenting" do @post.update(:tag_string => "parent:#{@post.id}") assert_nil(@post.parent_id) end should "clear the parent with parent:none" do @post.update(:parent_id => @parent.id) assert_equal(@parent.id, @post.parent_id) @post.update(:tag_string => "parent:none") assert_nil(@post.parent_id) end should "clear the parent with -parent:1234" do @post.update(:parent_id => @parent.id) assert_equal(@parent.id, @post.parent_id) @post.update(:tag_string => "-parent:#{@parent.id}") assert_nil(@post.parent_id) end end context "for a pool" do setup do mock_pool_archive_service! start_pool_archive_transaction end teardown do rollback_pool_archive_transaction end context "on creation" do setup do @pool = FactoryGirl.create(:pool) @post = FactoryGirl.create(:post, :tag_string => "aaa pool:#{@pool.id}") end should "add the post to the pool" do @post.reload @pool.reload assert_equal("#{@post.id}", @pool.post_ids) assert_equal("pool:#{@pool.id} pool:series", @post.pool_string) end end context "negated" do setup do @pool = FactoryGirl.create(:pool) @post = FactoryGirl.create(:post, :tag_string => "aaa") @post.add_pool!(@pool) @post.tag_string = "aaa -pool:#{@pool.id}" @post.save end should "remove the post from the pool" do @post.reload @pool.reload assert_equal("", @pool.post_ids) assert_equal("", @post.pool_string) end end context "id" do setup do @pool = FactoryGirl.create(:pool) @post.update_attributes(:tag_string => "aaa pool:#{@pool.id}") end should "add the post to the pool" do @post.reload @pool.reload assert_equal("#{@post.id}", @pool.post_ids) assert_equal("pool:#{@pool.id} pool:series", @post.pool_string) end end context "name" do context "that exists" do setup do @pool = FactoryGirl.create(:pool, :name => "abc") @post.update_attributes(:tag_string => "aaa pool:abc") end should "add the post to the pool" do @post.reload @pool.reload assert_equal("#{@post.id}", @pool.post_ids) assert_equal("pool:#{@pool.id} pool:series", @post.pool_string) end end context "that doesn't exist" do should "create a new pool and add the post to that pool" do @post.update_attributes(:tag_string => "aaa newpool:abc") @pool = Pool.find_by_name("abc") @post.reload assert_not_nil(@pool) assert_equal("#{@post.id}", @pool.post_ids) assert_equal("pool:#{@pool.id} pool:series", @post.pool_string) end end context "with special characters" do should "not strip '%' from the name" do @post.update(tag_string: "aaa newpool:ichigo_100%") assert(Pool.exists?(name: "ichigo_100%")) end end end end context "for a rating" do context "that is valid" do should "update the rating if the post is unlocked" do @post.update_attributes(:tag_string => "aaa rating:e") @post.reload assert_equal("e", @post.rating) end end context "that is invalid" do should "not update the rating" do @post.update_attributes(:tag_string => "aaa rating:z") @post.reload assert_equal("q", @post.rating) end end context "that is locked" do should "change the rating if locked in the same update" do @post.update({ :tag_string => "rating:e", :is_rating_locked => true }, :as => :builder) assert(@post.valid?) assert_equal("e", @post.reload.rating) end should "not change the rating if locked previously" do @post.is_rating_locked = true @post.save @post.update(:tag_string => "rating:e") assert(@post.invalid?) assert_not_equal("e", @post.reload.rating) end end end context "for a fav" do should "add/remove the current user to the post's favorite listing" do @post.update_attributes(:tag_string => "aaa fav:self") assert_equal("fav:#{@user.id}", @post.fav_string) @post.update_attributes(:tag_string => "aaa -fav:self") assert_equal("", @post.fav_string) end end context "for a child" do setup do @child = FactoryGirl.create(:post) end should "update the parent relationships for both posts" do @post.update_attributes(:tag_string => "aaa child:#{@child.id}") @post.reload @child.reload assert_equal(@post.id, @child.parent_id) assert(@post.has_children?) end end context "for a source" do should "set the source with source:foo_bar_baz" do @post.update(:tag_string => "source:foo_bar_baz") assert_equal("foo_bar_baz", @post.source) end should 'set the source with source:"foo bar baz"' do @post.update(:tag_string => 'source:"foo bar baz"') assert_equal("foo bar baz", @post.source) end should 'strip the source with source:" foo bar baz "' do @post.update(:tag_string => 'source:" foo bar baz "') assert_equal("foo bar baz", @post.source) end should "clear the source with source:none" do @post.update(:source => "foobar") @post.update(:tag_string => "source:none") assert_equal("", @post.source) end should "set the pixiv id with source:https://img18.pixiv.net/img/evazion/14901720.png" do @post.update(:tag_string => "source:https://img18.pixiv.net/img/evazion/14901720.png") assert_equal(14901720, @post.pixiv_id) end end context "of" do setup do @builder = FactoryGirl.create(:builder_user) end context "locked:notes" do context "by a member" do should "not lock the notes" do @post.update(:tag_string => "locked:notes") assert_equal(false, @post.is_note_locked) end end context "by a builder" do should "lock/unlock the notes" do CurrentUser.scoped(@builder) do @post.update(:tag_string => "locked:notes") assert_equal(true, @post.is_note_locked) @post.update(:tag_string => "-locked:notes") assert_equal(false, @post.is_note_locked) end end end end context "locked:rating" do context "by a member" do should "not lock the rating" do @post.update(:tag_string => "locked:rating") assert_equal(false, @post.is_rating_locked) end end context "by a builder" do should "lock/unlock the rating" do CurrentUser.scoped(@builder) do @post.update(:tag_string => "locked:rating") assert_equal(true, @post.is_rating_locked) @post.update(:tag_string => "-locked:rating") assert_equal(false, @post.is_rating_locked) end end end end context "locked:status" do context "by a member" do should "not lock the status" do @post.update(:tag_string => "locked:status") assert_equal(false, @post.is_status_locked) end end context "by an admin" do should "lock/unlock the status" do CurrentUser.scoped(FactoryGirl.create(:admin_user)) do @post.update(:tag_string => "locked:status") assert_equal(true, @post.is_status_locked) @post.update(:tag_string => "-locked:status") assert_equal(false, @post.is_status_locked) end end end end end context "of" do setup do @gold = FactoryGirl.create(:gold_user) end context "upvote:self or downvote:self" do context "by a member" do should "not upvote the post" do assert_raises PostVote::Error do @post.update(:tag_string => "upvote:self") end assert_equal(0, @post.score) end should "not downvote the post" do assert_raises PostVote::Error do @post.update(:tag_string => "downvote:self") end assert_equal(0, @post.score) end end context "by a gold user" do should "upvote the post" do CurrentUser.scoped(FactoryGirl.create(:gold_user)) do @post.update(:tag_string => "tag1 tag2 upvote:self") assert_equal(false, @post.errors.any?) assert_equal(1, @post.score) end end should "downvote the post" do CurrentUser.scoped(FactoryGirl.create(:gold_user)) do @post.update(:tag_string => "tag1 tag2 downvote:self") assert_equal(false, @post.errors.any?) assert_equal(-1, @post.score) end end end end end end context "tagged with a negated tag" do should "remove the tag if present" do @post.update_attributes(:tag_string => "aaa bbb ccc") @post.update_attributes(:tag_string => "aaa bbb ccc -bbb") @post.reload assert_equal("aaa ccc", @post.tag_string) end should "resolve aliases" do FactoryGirl.create(:tag_alias, :antecedent_name => "/tr", :consequent_name => "translation_request") @post.update(:tag_string => "aaa translation_request -/tr") assert_equal("aaa", @post.tag_string) end end should "have an array representation of its tags" do post = FactoryGirl.create(:post) post.set_tag_string("aaa bbb") assert_equal(%w(aaa bbb), post.tag_array) assert_equal(%w(tag1 tag2), post.tag_array_was) end context "with large dimensions" do setup do @post.image_width = 10_000 @post.image_height = 10 @post.tag_string = "" @post.save end should "have the appropriate dimension tags added automatically" do assert_match(/incredibly_absurdres/, @post.tag_string) assert_match(/absurdres/, @post.tag_string) assert_match(/highres/, @post.tag_string) end end context "with a large file size" do setup do @post.file_size = 11.megabytes @post.tag_string = "" @post.save end should "have the appropriate file size tags added automatically" do assert_match(/huge_filesize/, @post.tag_string) end end context "with a .zip file extension" do setup do @post.file_ext = "zip" @post.tag_string = "" @post.save end should "have the appropriate file type tag added automatically" do assert_match(/ugoira/, @post.tag_string) end end context "with a .webm file extension" do setup do @post.file_ext = "webm" @post.tag_string = "" @post.save end should "have the appropriate file type tag added automatically" do assert_match(/webm/, @post.tag_string) end end context "with a .swf file extension" do setup do @post.file_ext = "swf" @post.tag_string = "" @post.save end should "have the appropriate file type tag added automatically" do assert_match(/flash/, @post.tag_string) end end context "with *_(cosplay) tags" do setup do @post.add_tag("hakurei_reimu_(cosplay)") @post.add_tag("hatsune_miku_(cosplay)") @post.save end should "add the character tags and the cosplay tag" do assert(@post.has_tag?("hakurei_reimu")) assert(@post.has_tag?("hatsune_miku")) assert(@post.has_tag?("cosplay")) end end context "that has been updated" do should "create a new version if it's the first version" do assert_difference("PostArchive.count", 1) do post = FactoryGirl.create(:post) end end should "create a new version if it's been over an hour since the last update" do post = FactoryGirl.create(:post) Timecop.travel(6.hours.from_now) do assert_difference("PostArchive.count", 1) do post.update_attributes(:tag_string => "zzz") end end end should "merge with the previous version if the updater is the same user and it's been less than an hour" do post = FactoryGirl.create(:post) assert_difference("PostArchive.count", 0) do post.update_attributes(:tag_string => "zzz") end assert_equal("zzz", post.versions.last.tags) end should "increment the updater's post_update_count" do PostArchive.sqs_service.stubs(:merge?).returns(false) post = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") CurrentUser.reload assert_difference("CurrentUser.post_update_count", 1) do post.update_attributes(:tag_string => "zzz") CurrentUser.reload end end should "reset its tag array cache" do post = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") user = FactoryGirl.create(:user) assert_equal(%w(aaa bbb ccc), post.tag_array) post.tag_string = "ddd eee fff" post.tag_string = "ddd eee fff" post.save assert_equal("ddd eee fff", post.tag_string) assert_equal(%w(ddd eee fff), post.tag_array) end should "create the actual tag records" do assert_difference("Tag.count", 3) do post = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") end end should "update the post counts of relevant tag records" do post1 = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") post2 = FactoryGirl.create(:post, :tag_string => "bbb ccc ddd") post3 = FactoryGirl.create(:post, :tag_string => "ccc ddd eee") assert_equal(1, Tag.find_by_name("aaa").post_count) assert_equal(2, Tag.find_by_name("bbb").post_count) assert_equal(3, Tag.find_by_name("ccc").post_count) post3.tag_string = "xxx" post3.save assert_equal(1, Tag.find_by_name("aaa").post_count) assert_equal(2, Tag.find_by_name("bbb").post_count) assert_equal(2, Tag.find_by_name("ccc").post_count) assert_equal(1, Tag.find_by_name("ddd").post_count) assert_equal(0, Tag.find_by_name("eee").post_count) assert_equal(1, Tag.find_by_name("xxx").post_count) end should "update its tag counts" do artist_tag = FactoryGirl.create(:artist_tag) copyright_tag = FactoryGirl.create(:copyright_tag) general_tag = FactoryGirl.create(:tag) new_post = FactoryGirl.create(:post, :tag_string => "#{artist_tag.name} #{copyright_tag.name} #{general_tag.name}") assert_equal(1, new_post.tag_count_artist) assert_equal(1, new_post.tag_count_copyright) assert_equal(1, new_post.tag_count_general) assert_equal(0, new_post.tag_count_character) assert_equal(3, new_post.tag_count) new_post.tag_string = "babs" new_post.save assert_equal(0, new_post.tag_count_artist) assert_equal(0, new_post.tag_count_copyright) assert_equal(1, new_post.tag_count_general) assert_equal(0, new_post.tag_count_character) assert_equal(1, new_post.tag_count) end should "merge any tag changes that were made after loading the initial set of tags part 1" do post = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") # user a adds post_edited_by_user_a = Post.find(post.id) post_edited_by_user_a.old_tag_string = "aaa bbb ccc" post_edited_by_user_a.tag_string = "aaa bbb ccc ddd" post_edited_by_user_a.save # user b removes adds post_edited_by_user_b = Post.find(post.id) post_edited_by_user_b.old_tag_string = "aaa bbb ccc" post_edited_by_user_b.tag_string = "aaa bbb eee" post_edited_by_user_b.save # final should be , , , final_post = Post.find(post.id) assert_equal(%w(aaa bbb ddd eee), Tag.scan_tags(final_post.tag_string).sort) end should "merge any tag changes that were made after loading the initial set of tags part 2" do # This is the same as part 1, only the order of operations is reversed. # The results should be the same. post = FactoryGirl.create(:post, :tag_string => "aaa bbb ccc") # user a removes adds post_edited_by_user_a = Post.find(post.id) post_edited_by_user_a.old_tag_string = "aaa bbb ccc" post_edited_by_user_a.tag_string = "aaa bbb eee" post_edited_by_user_a.save # user b adds post_edited_by_user_b = Post.find(post.id) post_edited_by_user_b.old_tag_string = "aaa bbb ccc" post_edited_by_user_b.tag_string = "aaa bbb ccc ddd" post_edited_by_user_b.save # final should be , , , final_post = Post.find(post.id) assert_equal(%w(aaa bbb ddd eee), Tag.scan_tags(final_post.tag_string).sort) end should "merge any parent, source, and rating changes that were made after loading the initial set" do post = FactoryGirl.create(:post, :parent => nil, :source => "", :rating => "q") parent_post = FactoryGirl.create(:post) # user a changes rating to safe, adds parent post_edited_by_user_a = Post.find(post.id) post_edited_by_user_a.old_parent_id = "" post_edited_by_user_a.old_source = "" post_edited_by_user_a.old_rating = "q" post_edited_by_user_a.parent_id = parent_post.id post_edited_by_user_a.source = nil post_edited_by_user_a.rating = "s" post_edited_by_user_a.save # user b adds source post_edited_by_user_b = Post.find(post.id) post_edited_by_user_b.old_parent_id = "" post_edited_by_user_b.old_source = "" post_edited_by_user_b.old_rating = "q" post_edited_by_user_b.parent_id = nil post_edited_by_user_b.source = "http://example.com" post_edited_by_user_b.rating = "q" post_edited_by_user_b.save # final post should be rated safe and have the set parent and source final_post = Post.find(post.id) assert_equal(parent_post.id, final_post.parent_id) assert_equal("http://example.com", final_post.source) assert_equal("s", final_post.rating) end end context "that has been tagged with a metatag" do should "not include the metatag in its tag string" do post = FactoryGirl.create(:post) post.tag_string = "aaa pool:1234 pool:test rating:s fav:bob" post.save assert_equal("aaa", post.tag_string) end end context "with a source" do context "that is not from pixiv" do should "clear the pixiv id" do @post.pixiv_id = 1234 @post.update(source: "http://fc06.deviantart.net/fs71/f/2013/295/d/7/you_are_already_dead__by_mar11co-d6rgm0e.jpg") assert_nil(@post.pixiv_id) @post.pixiv_id = 1234 @post.update(source: "http://pictures.hentai-foundry.com//a/AnimeFlux/219123.jpg") assert_nil(@post.pixiv_id) end end context "that is from pixiv" do should "save the pixiv id" do @post.update(source: "https://img18.pixiv.net/img/evazion/14901720.png") assert_equal(14901720, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://img18.pixiv.net/img/evazion/14901720.png") assert_equal(14901720, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i2.pixiv.net/img18/img/evazion/14901720.png") assert_equal(14901720, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i2.pixiv.net/img18/img/evazion/14901720_m.png") assert_equal(14901720, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i2.pixiv.net/img18/img/evazion/14901720_s.png") assert_equal(14901720, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img07/img/pasirism/18557054_p1.png") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img07/img/pasirism/18557054_big_p1.png") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img-inf/img/2011/05/01/23/28/04/18557054_64x64.jpg") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img-inf/img/2011/05/01/23/28/04/18557054_s.png") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/c/600x600/img-master/img/2014/10/02/13/51/23/46304396_p0_master1200.jpg") assert_equal(46304396, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img-original/img/2014/10/02/13/51/23/46304396_p0.png") assert_equal(46304396, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://i1.pixiv.net/img-zip-ugoira/img/2014/10/03/17/29/16/46323924_ugoira1920x1080.zip") assert_equal(46323924, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://www.pixiv.net/member_illust.php?mode=big&illust_id=18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://www.pixiv.net/member_illust.php?mode=manga&illust_id=18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://www.pixiv.net/member_illust.php?mode=manga_big&illust_id=18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil @post.update(source: "http://www.pixiv.net/i/18557054") assert_equal(18557054, @post.pixiv_id) @post.pixiv_id = nil end context "but doesn't have a pixiv id" do should "not save the pixiv id" do @post.pixiv_id = 1234 @post.update(source: "http://i1.pixiv.net/novel-cover-original/img/2016/11/03/20/10/58/7436075_f75af69f3eacd1656d3733c72aa959cf.jpg") assert_nil(@post.pixiv_id) @post.pixiv_id = 1234 @post.update(source: "http://i2.pixiv.net/background/img/2016/10/30/12/27/30/7059005_da9946b806c10d391a81ed1117cd33d6.jpg") assert_nil(@post.pixiv_id) @post.pixiv_id = 1234 @post.update(source: "http://i1.pixiv.net/img15/img/omega777/novel/2612734.jpg") assert_nil(@post.pixiv_id) @post.pixiv_id = 1234 @post.update(source: "http://img08.pixiv.net/profile/nice/1408837.jpg") assert_nil(@post.pixiv_id) end end end should "normalize pixiv links" do @post.source = "http://i2.pixiv.net/img12/img/zenze/39749565.png" assert_equal("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=39749565", @post.normalized_source) @post.source = "http://i1.pixiv.net/img53/img/themare/39735353_big_p1.jpg" assert_equal("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=39735353", @post.normalized_source) @post.source = "http://i1.pixiv.net/c/150x150/img-master/img/2010/11/30/08/39/58/14901720_p0_master1200.jpg" assert_equal("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=14901720", @post.normalized_source) @post.source = "http://i1.pixiv.net/img-original/img/2010/11/30/08/39/58/14901720_p0.png" assert_equal("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=14901720", @post.normalized_source) @post.source = "http://i2.pixiv.net/img-zip-ugoira/img/2014/08/05/06/01/10/44524589_ugoira1920x1080.zip" assert_equal("http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44524589", @post.normalized_source) end should "normalize nicoseiga links" do @post.source = "http://lohas.nicoseiga.jp/priv/3521156?e=1382558156&h=f2e089256abd1d453a455ec8f317a6c703e2cedf" assert_equal("http://seiga.nicovideo.jp/seiga/im3521156", @post.normalized_source) @post.source = "http://lohas.nicoseiga.jp/priv/b80f86c0d8591b217e7513a9e175e94e00f3c7a1/1384936074/3583893" assert_equal("http://seiga.nicovideo.jp/seiga/im3583893", @post.normalized_source) end should "normalize twitpic links" do @post.source = "http://d3j5vwomefv46c.cloudfront.net/photos/large/820960031.jpg?1384107199" assert_equal("http://twitpic.com/dks0tb", @post.normalized_source) end should "normalize deviantart links" do @post.source = "http://fc06.deviantart.net/fs71/f/2013/295/d/7/you_are_already_dead__by_mar11co-d6rgm0e.jpg" assert_equal("http://mar11co.deviantart.com/art/You-Are-Already-Dead-408921710", @post.normalized_source) @post.source = "http://fc00.deviantart.net/fs71/f/2013/337/3/5/35081351f62b432f84eaeddeb4693caf-d6wlrqs.jpg" assert_equal("http://deviantart.com/deviation/417560500", @post.normalized_source) end should "normalize karabako links" do @post.source = "http://www.karabako.net/images/karabako_38835.jpg" assert_equal("http://www.karabako.net/post/view/38835", @post.normalized_source) end should "normalize twipple links" do @post.source = "http://p.twpl.jp/show/orig/mI2c3" assert_equal("http://p.twipple.jp/mI2c3", @post.normalized_source) end should "normalize hentai foundry links" do @post.source = "http://pictures.hentai-foundry.com//a/AnimeFlux/219123.jpg" assert_equal("http://www.hentai-foundry.com/pictures/user/AnimeFlux/219123", @post.normalized_source) @post.source = "http://pictures.hentai-foundry.com/a/AnimeFlux/219123/Mobile-Suit-Equestria-rainbow-run.jpg" assert_equal("http://www.hentai-foundry.com/pictures/user/AnimeFlux/219123", @post.normalized_source) end end end end context "Updating:" do context "A rating unlocked post" do setup { @post = FactoryGirl.create(:post) } subject { @post } should_not allow_value("S", "safe", "derp").for(:rating) should allow_value("s", "q", "e").for(:rating) end context "A rating locked post" do setup { @post = FactoryGirl.create(:post, :is_rating_locked => true) } subject { @post } should_not allow_value("S", "safe", "derp").for(:rating) should_not allow_value("s", "e").for(:rating) end end context "Favorites:" do context "Removing a post from a user's favorites" do setup do @user = FactoryGirl.create(:contributor_user) @post = FactoryGirl.create(:post) @post.add_favorite!(@user) @user.reload end should "decrement the user's favorite_count" do assert_difference("@user.favorite_count", -1) do @post.remove_favorite!(@user) end end should "decrement the post's score for gold users" do assert_difference("@post.score", -1) do @post.remove_favorite!(@user) end end should "not decrement the post's score for basic users" do @member = FactoryGirl.create(:user) assert_no_difference("@post.score") { @post.add_favorite!(@member) } assert_no_difference("@post.score") { @post.remove_favorite!(@member) } end should "not decrement the user's favorite_count if the user did not favorite the post" do @post2 = FactoryGirl.create(:post) assert_no_difference("@user.favorite_count") do @post2.remove_favorite!(@user) end end end context "Adding a post to a user's favorites" do setup do @user = FactoryGirl.create(:contributor_user) @post = FactoryGirl.create(:post) end should "periodically clean the fav_string" do @post.update_column(:fav_string, "fav:1 fav:1 fav:1") @post.update_column(:fav_count, 3) @post.stubs(:clean_fav_string?).returns(true) @post.append_user_to_fav_string(2) assert_equal("fav:1 fav:2", @post.fav_string) assert_equal(2, @post.fav_count) end should "increment the user's favorite_count" do assert_difference("@user.favorite_count", 1) do @post.add_favorite!(@user) end end should "increment the post's score for gold users" do @post.add_favorite!(@user) assert_equal(1, @post.score) end should "not increment the post's score for basic users" do @member = FactoryGirl.create(:user) @post.add_favorite!(@member) assert_equal(0, @post.score) end should "update the fav strings ont he post" do @post.add_favorite!(@user) @post.reload assert_equal("fav:#{@user.id}", @post.fav_string) assert(Favorite.exists?(:user_id => @user.id, :post_id => @post.id)) @post.add_favorite!(@user) @post.reload assert_equal("fav:#{@user.id}", @post.fav_string) assert(Favorite.exists?(:user_id => @user.id, :post_id => @post.id)) @post.remove_favorite!(@user) @post.reload assert_equal("", @post.fav_string) assert(!Favorite.exists?(:user_id => @user.id, :post_id => @post.id)) @post.remove_favorite!(@user) @post.reload assert_equal("", @post.fav_string) assert(!Favorite.exists?(:user_id => @user.id, :post_id => @post.id)) end end context "Moving favorites to a parent post" do setup do @parent = FactoryGirl.create(:post) @child = FactoryGirl.create(:post, parent: @parent) @user1 = FactoryGirl.create(:user, enable_privacy_mode: true) @gold1 = FactoryGirl.create(:gold_user) @supervoter1 = FactoryGirl.create(:user, is_super_voter: true) @child.add_favorite!(@user1) @child.add_favorite!(@gold1) @child.add_favorite!(@supervoter1) @parent.add_favorite!(@supervoter1) @child.give_favorites_to_parent @child.reload @parent.reload end should "move the favorites" do assert_equal(0, @child.fav_count) assert_equal(0, @child.favorites.count) assert_equal("", @child.fav_string) assert_equal([], @child.favorites.pluck(:user_id)) assert_equal(3, @parent.fav_count) assert_equal(3, @parent.favorites.count) end should "create a vote for each user who can vote" do assert(@parent.votes.where(user: @gold1).exists?) assert(@parent.votes.where(user: @supervoter1).exists?) assert_equal(4, @parent.score) end end end context "Pools:" do setup do SqsService.any_instance.stubs(:send_message) end context "Removing a post from a pool" do should "update the post's pool string" do post = FactoryGirl.create(:post) pool = FactoryGirl.create(:pool) post.add_pool!(pool) post.remove_pool!(pool) post.reload assert_equal("", post.pool_string) post.remove_pool!(pool) post.reload assert_equal("", post.pool_string) end end context "Adding a post to a pool" do should "update the post's pool string" do post = FactoryGirl.create(:post) pool = FactoryGirl.create(:pool) post.add_pool!(pool) post.reload assert_equal("pool:#{pool.id} pool:series", post.pool_string) post.add_pool!(pool) post.reload assert_equal("pool:#{pool.id} pool:series", post.pool_string) post.remove_pool!(pool) post.reload assert_equal("", post.pool_string) end end end context "Uploading:" do context "Uploading a post" do should "capture who uploaded the post" do post = FactoryGirl.create(:post) user1 = FactoryGirl.create(:user) user2 = FactoryGirl.create(:user) user3 = FactoryGirl.create(:user) post.uploader = user1 assert_equal(user1.id, post.uploader_id) post.uploader_id = user2.id assert_equal(user2.id, post.uploader_id) assert_equal(user2.id, post.uploader_id) assert_equal(user2.name, post.uploader_name) end end end context "Searching:" do setup do mock_pool_archive_service! end should "return posts for the age:<1minute tag" do post = FactoryGirl.create(:post) assert_tag_match([post], "age:<1minute") end should "return posts for the age:<1minute tag when the user is in Pacific time zone" do post = FactoryGirl.create(:post) Time.zone = "Pacific Time (US & Canada)" assert_tag_match([post], "age:<1minute") Time.zone = "Eastern Time (US & Canada)" end should "return posts for the age:<1minute tag when the user is in Tokyo time zone" do post = FactoryGirl.create(:post) Time.zone = "Asia/Tokyo" assert_tag_match([post], "age:<1minute") Time.zone = "Eastern Time (US & Canada)" end should "return posts for the ' tag" do post1 = FactoryGirl.create(:post, :tag_string => "'") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") assert_tag_match([post1], "'") end should "return posts for the \\ tag" do post1 = FactoryGirl.create(:post, :tag_string => "\\") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") assert_tag_match([post1], "\\") end should "return posts for the ( tag" do post1 = FactoryGirl.create(:post, :tag_string => "(") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") assert_tag_match([post1], "(") end should "return posts for the ? tag" do post1 = FactoryGirl.create(:post, :tag_string => "?") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") assert_tag_match([post1], "?") end should "return posts for 1 tag" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post2, post1], "aaa") end should "return posts for a 2 tag join" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post2], "aaa bbb") end should "return posts for a 2 tag union" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaab bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post3, post1], "~aaa ~ccc") end should "return posts for 1 tag with exclusion" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaa bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post1], "aaa -bbb") end should "return posts for 1 tag with a pattern" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaab bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post2, post1], "a*") end should "return posts for 2 tags, one with a pattern" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") post2 = FactoryGirl.create(:post, :tag_string => "aaab bbb") post3 = FactoryGirl.create(:post, :tag_string => "bbb ccc") assert_tag_match([post2], "a* bbb") end should "return posts for the id: metatag" do posts = FactoryGirl.create_list(:post, 3) assert_tag_match([posts[1]], "id:#{posts[1].id}") assert_tag_match([posts[2]], "id:>#{posts[1].id}") assert_tag_match([posts[0]], "id:<#{posts[1].id}") assert_tag_match([posts[2], posts[0]], "-id:#{posts[1].id}") assert_tag_match([posts[2], posts[1]], "id:>=#{posts[1].id}") assert_tag_match([posts[1], posts[0]], "id:<=#{posts[1].id}") assert_tag_match([posts[2], posts[0]], "id:#{posts[0].id},#{posts[2].id}") assert_tag_match(posts.reverse, "id:#{posts[0].id}..#{posts[2].id}") end should "return posts for the fav: metatag" do users = FactoryGirl.create_list(:user, 2) posts = users.map do |u| CurrentUser.scoped(u) { FactoryGirl.create(:post, tag_string: "fav:#{u.name}") } end assert_tag_match([posts[0]], "fav:#{users[0].name}") assert_tag_match([posts[1]], "-fav:#{users[0].name}") end should "return posts for the ordfav: metatag" do post1 = FactoryGirl.create(:post, tag_string: "fav:#{CurrentUser.name}") post2 = FactoryGirl.create(:post, tag_string: "fav:#{CurrentUser.name}") assert_tag_match([post2, post1], "ordfav:#{CurrentUser.name}") end should "return posts for the pool: metatag" do SqsService.any_instance.stubs(:send_message) FactoryGirl.create(:pool, name: "test_a", category: "series") FactoryGirl.create(:pool, name: "test_b", category: "collection") post1 = FactoryGirl.create(:post, tag_string: "pool:test_a") post2 = FactoryGirl.create(:post, tag_string: "pool:test_b") assert_tag_match([post1], "pool:test_a") assert_tag_match([post2], "-pool:test_a") assert_tag_match([], "-pool:test_a -pool:test_b") assert_tag_match([post2, post1], "pool:test*") assert_tag_match([post2, post1], "pool:any") assert_tag_match([], "pool:none") assert_tag_match([post1], "pool:series") assert_tag_match([post2], "-pool:series") assert_tag_match([post2], "pool:collection") assert_tag_match([post1], "-pool:collection") end should "return posts for the ordpool: metatag" do posts = FactoryGirl.create_list(:post, 2, tag_string: "newpool:test") assert_tag_match(posts, "ordpool:test") end should "return posts for the parent: metatag" do parent = FactoryGirl.create(:post) child = FactoryGirl.create(:post, tag_string: "parent:#{parent.id}") assert_tag_match([parent], "parent:none") assert_tag_match([child], "-parent:none") assert_tag_match([child, parent], "parent:#{parent.id}") assert_tag_match([child], "parent:#{child.id}") assert_tag_match([child], "child:none") assert_tag_match([parent], "child:any") end should "return posts for the favgroup: metatag" do favgroups = FactoryGirl.create_list(:favorite_group, 2, creator: CurrentUser.user) posts = favgroups.map { |g| FactoryGirl.create(:post, tag_string: "favgroup:#{g.name}") } assert_tag_match([posts[0]], "favgroup:#{favgroups[0].name}") assert_tag_match([posts[1]], "-favgroup:#{favgroups[0].name}") assert_tag_match([], "-favgroup:#{favgroups[0].name} -favgroup:#{favgroups[1].name}") end should "return posts for the user: metatag" do users = FactoryGirl.create_list(:user, 2) posts = users.map { |u| FactoryGirl.create(:post, uploader: u) } assert_tag_match([posts[0]], "user:#{users[0].name}") assert_tag_match([posts[1]], "-user:#{users[0].name}") end should "return posts for the approver: metatag" do users = FactoryGirl.create_list(:user, 2) posts = users.map { |u| FactoryGirl.create(:post, approver: u) } posts << FactoryGirl.create(:post, approver: nil) assert_tag_match([posts[0]], "approver:#{users[0].name}") assert_tag_match([posts[1]], "-approver:#{users[0].name}") assert_tag_match([posts[1], posts[0]], "approver:any") assert_tag_match([posts[2]], "approver:none") end should "return posts for the commenter: metatag" do users = FactoryGirl.create_list(:user, 2, created_at: 2.weeks.ago) posts = FactoryGirl.create_list(:post, 2) comms = users.zip(posts).map { |u, p| FactoryGirl.create(:comment, creator: u, post: p) } assert_tag_match([posts[0]], "commenter:#{users[0].name}") assert_tag_match([posts[1]], "commenter:#{users[1].name}") end should "return posts for the commenter: metatag" do posts = FactoryGirl.create_list(:post, 2) FactoryGirl.create(:comment, post: posts[0], is_deleted: false) FactoryGirl.create(:comment, post: posts[1], is_deleted: true) assert_tag_match([posts[0]], "commenter:any") assert_tag_match([posts[1]], "commenter:none") end should "return posts for the noter: metatag" do users = FactoryGirl.create_list(:user, 2) posts = FactoryGirl.create_list(:post, 2) notes = users.zip(posts).map { |u, p| FactoryGirl.create(:note, creator: u, post: p) } assert_tag_match([posts[0]], "noter:#{users[0].name}") assert_tag_match([posts[1]], "noter:#{users[1].name}") end should "return posts for the noter: metatag" do posts = FactoryGirl.create_list(:post, 2) FactoryGirl.create(:note, post: posts[0], is_active: true) FactoryGirl.create(:note, post: posts[1], is_active: false) assert_tag_match([posts[0]], "noter:any") assert_tag_match([posts[1]], "noter:none") end should "return posts for the artcomm: metatag" do users = FactoryGirl.create_list(:user, 2) posts = FactoryGirl.create_list(:post, 2) users.zip(posts).map do |u, p| CurrentUser.scoped(u) { FactoryGirl.create(:artist_commentary, post: p) } end assert_tag_match([posts[0]], "artcomm:#{users[0].name}") assert_tag_match([posts[1]], "artcomm:#{users[1].name}") end should "return posts for the date: metatag" do post = FactoryGirl.create(:post, created_at: Time.parse("2017-01-01")) assert_tag_match([post], "date:2017-01-01") end should "return posts for the age: metatag" do post = FactoryGirl.create(:post) assert_tag_match([post], "age:<60") assert_tag_match([post], "age:<60s") assert_tag_match([post], "age:<1mi") assert_tag_match([post], "age:<1h") assert_tag_match([post], "age:<1d") assert_tag_match([post], "age:<1w") assert_tag_match([post], "age:<1mo") assert_tag_match([post], "age:<1y") end should "return posts for the ratio: metatag" do post = FactoryGirl.create(:post, image_width: 1000, image_height: 500) assert_tag_match([post], "ratio:2:1") assert_tag_match([post], "ratio:2.0") end should "return posts for the status: metatag" do pending = FactoryGirl.create(:post, is_pending: true) flagged = FactoryGirl.create(:post, is_flagged: true) deleted = FactoryGirl.create(:post, is_deleted: true) banned = FactoryGirl.create(:post, is_banned: true) all = [banned, deleted, flagged, pending] assert_tag_match([pending], "status:pending") assert_tag_match([flagged], "status:flagged") assert_tag_match([deleted], "status:deleted") assert_tag_match([banned], "status:banned") assert_tag_match([flagged], "status:active") assert_tag_match(all, "status:any") assert_tag_match(all, "status:all") assert_tag_match(all - [pending], "-status:pending") assert_tag_match(all - [flagged], "-status:flagged") assert_tag_match(all - [deleted], "-status:deleted") assert_tag_match(all - [banned], "-status:banned") assert_tag_match(all - [flagged], "-status:active") end should "return posts for the filetype: metatag" do png = FactoryGirl.create(:post, file_ext: "png") jpg = FactoryGirl.create(:post, file_ext: "jpg") assert_tag_match([png], "filetype:png") assert_tag_match([jpg], "-filetype:png") end should "return posts for the tagcount: metatags" do post = FactoryGirl.create(:post, tag_string: "artist:wokada copyright:vocaloid char:hatsune_miku twintails") assert_tag_match([post], "tagcount:4") assert_tag_match([post], "arttags:1") assert_tag_match([post], "copytags:1") assert_tag_match([post], "chartags:1") assert_tag_match([post], "gentags:1") end should "return posts for the md5: metatag" do post1 = FactoryGirl.create(:post, :md5 => "abcd") post2 = FactoryGirl.create(:post) assert_tag_match([post1], "md5:abcd") end should "return posts for a source search" do post1 = FactoryGirl.create(:post, :source => "abcd") post2 = FactoryGirl.create(:post, :source => "abcdefg") post3 = FactoryGirl.create(:post, :source => "") assert_tag_match([post2], "source:abcde") assert_tag_match([post3, post1], "-source:abcde") assert_tag_match([post3], "source:none") assert_tag_match([post2, post1], "-source:none") end should "return posts for a case insensitive source search" do post1 = FactoryGirl.create(:post, :source => "ABCD") post2 = FactoryGirl.create(:post, :source => "1234") assert_tag_match([post1], "source:abcd") end should "return posts for a pixiv source search" do url = "http://i1.pixiv.net/img123/img/artist-name/789.png" post = FactoryGirl.create(:post, :source => url) assert_tag_match([post], "source:*.pixiv.net/img*/artist-name/*") assert_tag_match([], "source:*.pixiv.net/img*/artist-fake/*") assert_tag_match([post], "source:http://*.pixiv.net/img*/img/artist-name/*") assert_tag_match([], "source:http://*.pixiv.net/img*/img/artist-fake/*") assert_tag_match([post], "source:pixiv/artist-name/*") assert_tag_match([], "source:pixiv/artist-fake/*") end should "return posts for a pixiv id search (type 1)" do url = "http://i1.pixiv.net/img-inf/img/2013/03/14/03/02/36/34228050_s.jpg" post = FactoryGirl.create(:post, :source => url) assert_tag_match([post], "pixiv_id:34228050") end should "return posts for a pixiv id search (type 2)" do url = "http://i1.pixiv.net/img123/img/artist-name/789.png" post = FactoryGirl.create(:post, :source => url) assert_tag_match([post], "pixiv_id:789") end should "return posts for a pixiv id search (type 3)" do url = "http://www.pixiv.net/member_illust.php?mode=manga_big&illust_id=19113635&page=0" post = FactoryGirl.create(:post, :source => url) assert_tag_match([post], "pixiv_id:19113635") end should "return posts for a pixiv id search (type 4)" do url = "http://i2.pixiv.net/img70/img/disappearedstump/34551381_p3.jpg?1364424318" post = FactoryGirl.create(:post, :source => url) assert_tag_match([post], "pixiv_id:34551381") end # should "return posts for a pixiv novel id search" do # url = "http://www.pixiv.net/novel/show.php?id=2156088" # post = FactoryGirl.create(:post, :source => url) # assert_equal(1, Post.tag_match("pixiv_novel_id:2156088").count) # end should "return posts for a tag subscription search" do post1 = FactoryGirl.create(:post, :tag_string => "aaa") sub = FactoryGirl.create(:tag_subscription, :tag_query => "aaa", :name => "zzz") TagSubscription.process_all relation = Post.tag_match("sub:#{CurrentUser.name}") assert_equal(1, relation.count) end should "return posts for a search: metatag" do post1 = FactoryGirl.create(:post, tag_string: "aaa") post2 = FactoryGirl.create(:post, tag_string: "bbb") FactoryGirl.create(:saved_search, query: "aaa", labels: ["zzz"], user: CurrentUser.user) FactoryGirl.create(:saved_search, query: "bbb", user: CurrentUser.user) SavedSearch.expects(:post_ids).with(CurrentUser.id, "zzz").returns([post1.id]) SavedSearch.expects(:post_ids).with(CurrentUser.id, "uncategorized").returns([post2.id]) SavedSearch.expects(:post_ids).with(CurrentUser.id).returns([post1.id, post2.id]) assert_tag_match([post1], "search:zzz") assert_tag_match([post2], "search:uncategorized") assert_tag_match([post2, post1], "search:all") end should "return posts for a rating: metatag" do s = FactoryGirl.create(:post, :rating => "s") q = FactoryGirl.create(:post, :rating => "q") e = FactoryGirl.create(:post, :rating => "e") all = [e, q, s] assert_tag_match([s], "rating:s") assert_tag_match([q], "rating:q") assert_tag_match([e], "rating:e") assert_tag_match(all - [s], "-rating:s") assert_tag_match(all - [q], "-rating:q") assert_tag_match(all - [e], "-rating:e") end should "return posts for a locked: metatag" do rating_locked = FactoryGirl.create(:post, is_rating_locked: true) note_locked = FactoryGirl.create(:post, is_note_locked: true) status_locked = FactoryGirl.create(:post, is_status_locked: true) all = [status_locked, note_locked, rating_locked] assert_tag_match([rating_locked], "locked:rating") assert_tag_match([note_locked], "locked:note") assert_tag_match([status_locked], "locked:status") assert_tag_match(all - [rating_locked], "-locked:rating") assert_tag_match(all - [note_locked], "-locked:note") assert_tag_match(all - [status_locked], "-locked:status") end should "return posts for a upvote:, downvote: metatag" do CurrentUser.scoped(FactoryGirl.create(:mod_user)) do upvoted = FactoryGirl.create(:post, tag_string: "upvote:self") downvoted = FactoryGirl.create(:post, tag_string: "downvote:self") assert_tag_match([upvoted], "upvote:#{CurrentUser.name}") assert_tag_match([downvoted], "downvote:#{CurrentUser.name}") end end should "return posts ordered by a particular attribute" do posts = (1..2).map do |n| p = FactoryGirl.create( :post, score: n, fav_count: n, file_size: 1.megabyte * n, # posts[0] is portrait, posts[1] is landscape. posts[1].mpixels > posts[0].mpixels. image_height: 100*n*n, image_width: 100*(3-n)*n, ) FactoryGirl.create(:artist_commentary, post: p) FactoryGirl.create(:comment, post: p, do_not_bump_post: false) FactoryGirl.create(:note, post: p) p end assert_tag_match(posts.reverse, "order:id_desc") assert_tag_match(posts.reverse, "order:score") assert_tag_match(posts.reverse, "order:favcount") assert_tag_match(posts.reverse, "order:change") assert_tag_match(posts.reverse, "order:comment") assert_tag_match(posts.reverse, "order:comment_bumped") assert_tag_match(posts.reverse, "order:note") assert_tag_match(posts.reverse, "order:artcomm") assert_tag_match(posts.reverse, "order:mpixels") assert_tag_match(posts.reverse, "order:portrait") assert_tag_match(posts.reverse, "order:filesize") assert_tag_match(posts.reverse, "order:rank") assert_tag_match(posts, "order:id_asc") assert_tag_match(posts, "order:score_asc") assert_tag_match(posts, "order:favcount_asc") assert_tag_match(posts, "order:change_asc") assert_tag_match(posts, "order:comment_asc") assert_tag_match(posts, "order:comment_bumped_asc") assert_tag_match(posts, "order:artcomm_asc") assert_tag_match(posts, "order:note_asc") assert_tag_match(posts, "order:mpixels_asc") assert_tag_match(posts, "order:landscape") assert_tag_match(posts, "order:filesize_asc") end should "return posts for order:comment_bumped" do post1 = FactoryGirl.create(:post) post2 = FactoryGirl.create(:post) post3 = FactoryGirl.create(:post) CurrentUser.scoped(FactoryGirl.create(:gold_user), "127.0.0.1") do comment1 = FactoryGirl.create(:comment, :post => post1) comment2 = FactoryGirl.create(:comment, :post => post2, :do_not_bump_post => true) comment3 = FactoryGirl.create(:comment, :post => post3) end assert_tag_match([post3, post1, post2], "order:comment_bumped") assert_tag_match([post2, post1, post3], "order:comment_bumped_asc") end should "return posts for a filesize search" do post = FactoryGirl.create(:post, :file_size => 1.megabyte) assert_tag_match([post], "filesize:1mb") assert_tag_match([post], "filesize:1000kb") assert_tag_match([post], "filesize:1048576b") end should "not perform fuzzy matching for an exact filesize search" do post = FactoryGirl.create(:post, :file_size => 1.megabyte) assert_tag_match([], "filesize:1048000b") assert_tag_match([], "filesize:1048000") end should "fail for more than 6 tags" do post1 = FactoryGirl.create(:post, :rating => "s") assert_raise(::Post::SearchError) do Post.tag_match("a b c rating:s width:10 height:10 user:bob") end end should "succeed for exclusive tag searches with no other tag" do post1 = FactoryGirl.create(:post, :rating => "s", :tag_string => "aaa") assert_nothing_raised do relation = Post.tag_match("-aaa") end end should "succeed for exclusive tag searches combined with a metatag" do post1 = FactoryGirl.create(:post, :rating => "s", :tag_string => "aaa") assert_nothing_raised do relation = Post.tag_match("-aaa id:>0") end end end context "Voting:" do context "with a super voter" do setup do @user = FactoryGirl.create(:user) FactoryGirl.create(:super_voter, user: @user) @post = FactoryGirl.create(:post) end should "account for magnitude" do CurrentUser.scoped(@user, "127.0.0.1") do assert_nothing_raised {@post.vote!("up")} assert_raises(PostVote::Error) {@post.vote!("up")} @post.reload assert_equal(1, PostVote.count) assert_equal(SuperVoter::MAGNITUDE, @post.score) end end end should "not allow members to vote" do @user = FactoryGirl.create(:user) @post = FactoryGirl.create(:post) CurrentUser.scoped(@user) do assert_raises(PostVote::Error) { @post.vote!("up") } end end should "not allow duplicate votes" do user = FactoryGirl.create(:gold_user) post = FactoryGirl.create(:post) CurrentUser.scoped(user, "127.0.0.1") do assert_nothing_raised {post.vote!("up")} assert_raises(PostVote::Error) {post.vote!("up")} post.reload assert_equal(1, PostVote.count) assert_equal(1, post.score) end end should "allow undoing of votes" do user = FactoryGirl.create(:gold_user) post = FactoryGirl.create(:post) # We deliberately don't call post.reload until the end to verify that # post.unvote! returns the correct score even when not forcibly reloaded. CurrentUser.scoped(user, "127.0.0.1") do post.vote!("up") assert_equal(1, post.score) post.unvote! assert_equal(0, post.score) assert_nothing_raised {post.vote!("down")} assert_equal(-1, post.score) post.unvote! assert_equal(0, post.score) assert_nothing_raised {post.vote!("up")} assert_equal(1, post.score) post.reload assert_equal(1, post.score) end end end context "Counting:" do context "Creating a post" do setup do Danbooru.config.stubs(:blank_tag_search_fast_count).returns(nil) Danbooru.config.stubs(:estimate_post_counts).returns(false) end context "with a primed cache" do setup do Cache.put("pfc:aaa", 0) Cache.put("pfc:alias", 0) Cache.put("pfc:width:50", 0) Danbooru.config.stubs(:blank_tag_search_fast_count).returns(1_000_000) FactoryGirl.create(:tag_alias, :antecedent_name => "alias", :consequent_name => "aaa") FactoryGirl.create(:post, :tag_string => "aaa") end should "be counted correctly in fast_count" do assert_equal(1, Post.count) assert_equal(1, Post.fast_count("")) assert_equal(1, Post.fast_count("aaa")) assert_equal(1, Post.fast_count("alias")) assert_equal(0, Post.fast_count("bbb")) end end should "increment the post count" do assert_equal(0, Post.fast_count("")) post = FactoryGirl.create(:post, :tag_string => "aaa bbb") assert_equal(1, Post.fast_count("")) assert_equal(1, Post.fast_count("aaa")) assert_equal(1, Post.fast_count("bbb")) assert_equal(0, Post.fast_count("ccc")) post.tag_string = "ccc" post.save assert_equal(1, Post.fast_count("")) assert_equal(0, Post.fast_count("aaa")) assert_equal(0, Post.fast_count("bbb")) assert_equal(1, Post.fast_count("ccc")) end end context "The cache" do context "when shared between users on danbooru/safebooru" do setup do Danbooru.config.stubs(:blank_tag_search_fast_count).returns(nil) FactoryGirl.create(:post, :tag_string => "aaa bbb", :rating => "q") FactoryGirl.create(:post, :tag_string => "aaa bbb", :rating => "s") FactoryGirl.create(:post, :tag_string => "aaa bbb", :rating => "s") CurrentUser.stubs(:safe_mode?).returns(true) Post.fast_count("aaa") CurrentUser.stubs(:safe_mode?).returns(false) Post.fast_count("bbb") end should "be accurate on danbooru" do CurrentUser.stubs(:safe_mode?).returns(false) assert_equal(3, Post.fast_count("aaa")) assert_equal(3, Post.fast_count("bbb")) end should "be accurate on safebooru" do CurrentUser.stubs(:safe_mode?).returns(true) assert_equal(2, Post.fast_count("aaa")) assert_equal(2, Post.fast_count("bbb")) end end context "when shared between users with the deleted post filter on/off" do setup do FactoryGirl.create(:post, :tag_string => "aaa bbb", :is_deleted => true) FactoryGirl.create(:post, :tag_string => "aaa bbb", :is_deleted => false) FactoryGirl.create(:post, :tag_string => "aaa bbb", :is_deleted => false) CurrentUser.user.stubs(:hide_deleted_posts?).returns(true) Post.fast_count("aaa") CurrentUser.user.stubs(:hide_deleted_posts?).returns(false) Post.fast_count("bbb") end should "be accurate with the deleted post filter on" do CurrentUser.user.stubs(:hide_deleted_posts?).returns(true) assert_equal(2, Post.fast_count("aaa")) assert_equal(2, Post.fast_count("bbb")) end should "be accurate with the deleted post filter off" do CurrentUser.user.stubs(:hide_deleted_posts?).returns(false) assert_equal(3, Post.fast_count("aaa")) assert_equal(3, Post.fast_count("bbb")) end end end end context "Reverting: " do context "a post that is rating locked" do setup do @post = FactoryGirl.create(:post, :rating => "s") Timecop.travel(2.hours.from_now) do @post.update({ :rating => "q", :is_rating_locked => true }, :as => :builder) end end should "not revert the rating" do assert_raises ActiveRecord::RecordInvalid do @post.revert_to!(@post.versions.first) end assert_equal(["Rating is locked and cannot be changed. Unlock the post first."], @post.errors.full_messages) assert_equal(@post.versions.last.rating, @post.reload.rating) end should "revert the rating after unlocking" do @post.update({ :rating => "e", :is_rating_locked => false }, :as => :builder) assert_nothing_raised do @post.revert_to!(@post.versions.first) end assert(@post.valid?) assert_equal(@post.versions.first.rating, @post.rating) end end context "a post that has been updated" do setup do PostArchive.sqs_service.stubs(:merge?).returns(false) @post = FactoryGirl.create(:post, :rating => "q", :tag_string => "aaa", :source => "") @post.update_attributes(:tag_string => "aaa bbb ccc ddd") @post.update_attributes(:tag_string => "bbb xxx yyy", :source => "xyz") @post.update_attributes(:tag_string => "bbb mmm yyy", :source => "abc") end context "and then reverted to an early version" do setup do @post.revert_to(@post.versions[1]) end should "correctly revert all fields" do assert_equal("aaa bbb ccc ddd", @post.tag_string) assert_equal("", @post.source) assert_equal("q", @post.rating) end end context "and then reverted to a later version" do setup do @post.revert_to(@post.versions[-2]) end should "correctly revert all fields" do assert_equal("bbb xxx yyy", @post.tag_string) assert_equal("xyz", @post.source) assert_equal("q", @post.rating) end end end end context "Mass assignment: " do should_not allow_mass_assignment_of(:last_noted_at).as(:member) end end