diff --git a/test/unit/post_query_builder_test.rb b/test/unit/post_query_builder_test.rb new file mode 100644 index 000000000..894efaf0d --- /dev/null +++ b/test/unit/post_query_builder_test.rb @@ -0,0 +1,770 @@ +require 'test_helper' + +class PostQueryBuilderTest < ActiveSupport::TestCase + def assert_tag_match(posts, query) + assert_equal(posts.map(&:id), Post.tag_match(query).pluck(:id)) + end + + setup do + CurrentUser.user = create(:user) + CurrentUser.ip_addr = "127.0.0.1" + end + + teardown do + CurrentUser.user = nil + CurrentUser.ip_addr = nil + end + + context "Searching:" do + should "return posts for the age:<1minute tag" do + post = 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 = 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 = 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 = create(:post, tag_string: "'") + post2 = create(:post, tag_string: "aaa bbb") + + assert_tag_match([post1], "'") + end + + should "return posts for the \\ tag" do + post1 = create(:post, tag_string: "\\") + post2 = create(:post, tag_string: "aaa bbb") + + assert_tag_match([post1], "\\") + end + + should "return posts for the ( tag" do + post1 = create(:post, tag_string: "(") + post2 = create(:post, tag_string: "aaa bbb") + + assert_tag_match([post1], "(") + end + + should "return posts for the ? tag" do + post1 = create(:post, tag_string: "?") + post2 = create(:post, tag_string: "aaa bbb") + + assert_tag_match([post1], "?") + end + + should "return posts for 1 tag" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaa bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post2, post1], "aaa") + end + + should "return posts for a 2 tag join" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaa bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post2], "aaa bbb") + end + + should "return posts for a 2 tag union" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaab bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post3, post1], "~aaa ~ccc") + end + + should "return posts for 1 tag with exclusion" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaa bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post1], "aaa -bbb") + end + + should "return posts for 1 tag with a pattern" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaab bbb") + post3 = 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 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaab bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post2], "a* bbb") + end + + should "return posts for a negated pattern" do + post1 = create(:post, tag_string: "aaa") + post2 = create(:post, tag_string: "aaab bbb") + post3 = create(:post, tag_string: "bbb ccc") + + assert_tag_match([post3], "-a*") + assert_tag_match([post3], "bbb -a*") + assert_tag_match([post3], "~bbb -a*") + assert_tag_match([post1], "a* -*b") + assert_tag_match([post2], "-*c -a*a") + end + + should "ignore invalid operator syntax" do + assert_nothing_raised do + assert_tag_match([], "-") + assert_tag_match([], "~") + end + end + + should "return posts for the id: metatag" do + posts = 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 + user1 = create(:user) + user2 = create(:user) + user3 = create(:user, enable_private_favorites: true) + post1 = as(user1) { create(:post, tag_string: "fav:true") } + post2 = as(user2) { create(:post, tag_string: "fav:true") } + post3 = as(user3) { create(:post, tag_string: "fav:true") } + + assert_tag_match([post1], "fav:#{user1.name}") + assert_tag_match([post2], "fav:#{user2.name}") + assert_tag_match([], "fav:#{user3.name}") + assert_tag_match([], "fav:dne") + + assert_tag_match([post3, post2], "-fav:#{user1.name}") + assert_tag_match([post3, post2, post1], "-fav:dne") + + as(user3) do + assert_tag_match([post3], "fav:#{user3.name}") + assert_tag_match([post2, post1], "-fav:#{user3.name}") + end + end + + should "return posts for the ordfav: metatag" do + post1 = create(:post, tag_string: "fav:#{CurrentUser.name}") + post2 = 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) + + pool1 = create(:pool, name: "test_a", category: "series") + pool2 = create(:pool, name: "test_b", category: "collection") + post1 = create(:post, tag_string: "pool:test_a") + post2 = create(:post, tag_string: "pool:test_b") + + assert_tag_match([post1], "pool:#{pool1.id}") + assert_tag_match([post2], "pool:#{pool2.id}") + + assert_tag_match([post1], "pool:TEST_A") + assert_tag_match([post2], "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([], "-pool:test_a -pool:test_b") + + assert_tag_match([post2, post1], "pool:test*") + + assert_tag_match([post2, post1], "pool:any") + assert_tag_match([post2, post1], "-pool:none") + assert_tag_match([], "-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 = create_list(:post, 2, tag_string: "newpool:test") + + assert_tag_match(posts, "ordpool:test") + end + + should "return posts for the ordpool: metatag for a series pool containing duplicate posts" do + posts = create_list(:post, 2) + pool = create(:pool, name: "test", category: "series", post_ids: [posts[0].id, posts[1].id, posts[1].id]) + + assert_tag_match([posts[0], posts[1], posts[1]], "ordpool:test") + end + + should "return posts for the parent: metatag" do + parent = create(:post) + child = 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 + post1 = create(:post) + post2 = create(:post) + post3 = create(:post) + + favgroup1 = create(:favorite_group, creator: CurrentUser.user, post_ids: [post1.id]) + favgroup2 = create(:favorite_group, creator: CurrentUser.user, post_ids: [post2.id]) + favgroup3 = create(:favorite_group, creator: create(:user), post_ids: [post3.id], is_public: false) + + assert_tag_match([post1], "favgroup:#{favgroup1.id}") + assert_tag_match([post2], "favgroup:#{favgroup2.name}") + assert_tag_match([], "favgroup:#{favgroup3.name}") + assert_tag_match([], "favgroup:dne") + + assert_tag_match([post3, post2], "-favgroup:#{favgroup1.id}") + assert_tag_match([post3, post1], "-favgroup:#{favgroup2.name}") + assert_tag_match([post3, post2, post1], "-favgroup:#{favgroup3.name}") + assert_tag_match([post3, post2, post1], "-favgroup:dne") + + assert_tag_match([post3], "-favgroup:#{favgroup1.name} -favgroup:#{favgroup2.name}") + + as(favgroup3.creator) do + assert_tag_match([post1], "favgroup:#{favgroup1.id}") + assert_tag_match([post2], "favgroup:#{favgroup2.id}") + assert_tag_match([post3], "favgroup:#{favgroup3.id}") + + assert_tag_match([], "favgroup:#{favgroup1.name}") + assert_tag_match([], "favgroup:#{favgroup2.name}") + assert_tag_match([post3], "favgroup:#{favgroup3.name}") + end + end + + should "return posts for the user: metatag" do + users = create_list(:user, 2, created_at: 2.weeks.ago) + posts = users.map { |u| create(:post, uploader: u) } + + assert_tag_match([posts[0]], "user:#{users[0].name}") + assert_tag_match([posts[1]], "-user:#{users[0].name}") + assert_tag_match([posts[1]], "filetype:jpg -user:#{users[0].name}") + end + + should "return posts for the approver: metatag" do + users = create_list(:user, 2) + posts = users.map { |u| create(:post, approver: u) } + posts << 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 = create_list(:user, 2, created_at: 2.weeks.ago) + posts = create_list(:post, 2) + comms = users.zip(posts).map { |u, p| as(u) { 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 = create_list(:post, 2) + create(:comment, creator: create(:user, created_at: 2.weeks.ago), post: posts[0], is_deleted: false) + create(:comment, creator: create(:user, created_at: 2.weeks.ago), post: posts[1], is_deleted: true) + + assert_tag_match(posts.reverse, "commenter:any") + assert_tag_match([], "commenter:none") + end + + should "return posts for the noter: metatag" do + users = create_list(:user, 2) + posts = create_list(:post, 2) + notes = users.zip(posts).map do |u, p| + as(u) { create(:note, post: p) } + end + + 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 = create_list(:post, 2) + create(:note, post: posts[0], is_active: true) + create(:note, post: posts[1], is_active: false) + + assert_tag_match(posts.reverse, "noter:any") + assert_tag_match([], "noter:none") + end + + should "return posts for the note_count: metatag" do + posts = create_list(:post, 3) + create(:note, post: posts[0], is_active: true) + create(:note, post: posts[1], is_active: false) + + assert_tag_match([posts[1], posts[0]], "note_count:1") + assert_tag_match([posts[0]], "active_note_count:1") + assert_tag_match([posts[1]], "deleted_note_count:1") + + assert_tag_match([posts[1], posts[0]], "notes:1") + assert_tag_match([posts[0]], "active_notes:1") + assert_tag_match([posts[1]], "deleted_notes:1") + end + + should "return posts for the commentaryupdater: metatag" do + user1 = create(:user) + user2 = create(:user) + post1 = create(:post) + post2 = create(:post) + artcomm1 = as(user1) { create(:artist_commentary, post: post1) } + artcomm2 = as(user2) { create(:artist_commentary, post: post2) } + + assert_tag_match([post1], "commentaryupdater:#{user1.name}") + assert_tag_match([post2], "commentaryupdater:#{user2.name}") + assert_tag_match([post2], "-commentaryupdater:#{user1.name}") + assert_tag_match([post1], "-commentaryupdater:#{user2.name}") + + assert_tag_match([post1], "artcomm:#{user1.name}") + assert_tag_match([post2], "artcomm:#{user2.name}") + assert_tag_match([post2], "-artcomm:#{user1.name}") + assert_tag_match([post1], "-artcomm:#{user2.name}") + + assert_tag_match([post2, post1], "commentaryupdater:any") + assert_tag_match([], "commentaryupdater:none") + end + + should "return posts for the commentary: metatag" do + post1 = create(:post) + post2 = create(:post) + post3 = create(:post) + post4 = create(:post) + + artcomm1 = create(:artist_commentary, post: post1, translated_title: "azur lane") + artcomm2 = create(:artist_commentary, post: post2, translated_title: "", translated_description: "") + artcomm3 = create(:artist_commentary, post: post3, original_title: "", original_description: "", translated_title: "", translated_description: "") + + assert_tag_match([post2, post1], "commentary:true") + assert_tag_match([post4, post3], "commentary:false") + + assert_tag_match([post4, post3], "-commentary:true") + assert_tag_match([post2, post1], "-commentary:false") + + assert_tag_match([post1], "commentary:translated") + assert_tag_match([post4, post3, post2], "-commentary:translated") + + assert_tag_match([post2], "commentary:untranslated") + assert_tag_match([post4, post3, post1], "-commentary:untranslated") + + assert_tag_match([post1], 'commentary:"azur lane"') + assert_tag_match([post4, post3, post2], '-commentary:"azur lane"') + end + + should "return posts for the date: metatag" do + post = create(:post, created_at: Time.parse("2017-01-01 12:00")) + + assert_tag_match([post], "date:2017-01-01") + end + + should "return posts for the age: metatag" do + post = 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 = 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 = create(:post, is_pending: true) + flagged = create(:post, is_flagged: true) + deleted = create(:post, is_deleted: true) + banned = create(:post, is_banned: true) + all = [banned, deleted, flagged, pending] + + assert_tag_match([flagged, pending], "status:modqueue") + 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([], "status:active") + assert_tag_match(all, "status:any") + assert_tag_match(all, "status:all") + + assert_tag_match(all - [flagged, pending], "-status:modqueue") + 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, "-status:active") + end + + should "return posts for the status:unmoderated metatag" do + flagged = create(:post, is_flagged: true) + pending = create(:post, is_pending: true) + disapproved = create(:post, is_pending: true) + + create(:post_flag, post: flagged, creator: create(:user, created_at: 2.weeks.ago)) + create(:post_disapproval, user: CurrentUser.user, post: disapproved, reason: "disinterest") + + assert_tag_match([pending, flagged], "status:unmoderated") + end + + should "respect the 'Deleted post filter' option when using the status:banned metatag" do + deleted = create(:post, is_deleted: true, is_banned: true) + undeleted = create(:post, is_banned: true) + + CurrentUser.hide_deleted_posts = true + assert_tag_match([undeleted], "status:banned") + + CurrentUser.hide_deleted_posts = false + assert_tag_match([undeleted, deleted], "status:banned") + end + + should "return posts for the filetype: metatag" do + png = create(:post, file_ext: "png") + jpg = 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 = 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 = create(:post, md5: "abcd") + post2 = create(:post) + + assert_tag_match([post1], "md5:abcd") + end + + should "return posts for a source search" do + post1 = create(:post, source: "abcd") + post2 = create(:post, source: "abcdefg") + post3 = 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 = create(:post, source: "ABCD") + post2 = 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 = 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/*") + 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 = 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 = 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 = 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 = create(:post, source: url) + assert_tag_match([post], "pixiv_id:34551381") + end + + should "return posts for a pixiv_id:any search" do + url = "http://i1.pixiv.net/img-original/img/2014/10/02/13/51/23/46304396_p0.png" + post = create(:post, source: url) + assert_tag_match([post], "pixiv_id:any") + end + + should "return posts for a pixiv_id:none search" do + post = create(:post) + assert_tag_match([post], "pixiv_id:none") + end + + context "saved searches" do + setup do + @post1 = create(:post, tag_string: "aaa") + @post2 = create(:post, tag_string: "bbb") + create(:saved_search, query: "aaa", labels: ["zzz"], user: CurrentUser.user) + create(:saved_search, query: "bbb", user: CurrentUser.user) + end + + context "labeled" do + should "work" do + SavedSearch.expects(:post_ids_for).with(CurrentUser.id, label: "zzz").returns([@post1.id]) + assert_tag_match([@post1], "search:zzz") + end + end + + context "missing" do + should "work" do + SavedSearch.expects(:post_ids_for).with(CurrentUser.id, label: "uncategorized").returns([@post2.id]) + assert_tag_match([@post2], "search:uncategorized") + end + end + + context "all" do + should "work" do + SavedSearch.expects(:post_ids_for).with(CurrentUser.id).returns([@post1.id, @post2.id]) + assert_tag_match([@post2, @post1], "search:all") + end + end + end + + should "return posts for a rating: metatag" do + s = create(:post, rating: "s") + q = create(:post, rating: "q") + e = 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 = create(:post, is_rating_locked: true) + note_locked = create(:post, is_note_locked: true) + status_locked = 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(create(:mod_user)) do + upvoted = create(:post, tag_string: "upvote:self") + downvoted = 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 for a disapproved: metatag" do + CurrentUser.scoped(create(:mod_user)) do + pending = create(:post, is_pending: true) + disapproved = create(:post, is_pending: true) + disapproval = create(:post_disapproval, user: CurrentUser.user, post: disapproved, reason: "disinterest") + + assert_tag_match([disapproved], "disapproved:#{CurrentUser.name}") + assert_tag_match([disapproved], "disapproved:disinterest") + assert_tag_match([], "disapproved:breaks_rules") + + assert_tag_match([pending], "-disapproved:#{CurrentUser.name}") + assert_tag_match([pending], "-disapproved:disinterest") + assert_tag_match([disapproved, pending], "-disapproved:breaks_rules") + end + end + + should "return posts ordered by a particular attribute" do + posts = (1..2).map do |n| + tags = ["tagme", "gentag1 gentag2 artist:arttag char:chartag copy:copytag"] + + p = 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, + tag_string: tags[n - 1] + ) + + u = create(:user, created_at: 2.weeks.ago) + create(:artist_commentary, post: p) + create(:comment, post: p, creator: u, do_not_bump_post: false) + create(:note, post: p) + p + end + + create(:note, post: posts.second) + + 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:tagcount") + assert_tag_match(posts.reverse, "order:gentags") + assert_tag_match(posts.reverse, "order:arttags") + assert_tag_match(posts.reverse, "order:chartags") + assert_tag_match(posts.reverse, "order:copytags") + assert_tag_match(posts.reverse, "order:rank") + assert_tag_match(posts.reverse, "order:note_count") + assert_tag_match(posts.reverse, "order:note_count_desc") + assert_tag_match(posts.reverse, "order:notes") + assert_tag_match(posts.reverse, "order:notes_desc") + + 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") + assert_tag_match(posts, "order:tagcount_asc") + assert_tag_match(posts, "order:gentags_asc") + assert_tag_match(posts, "order:arttags_asc") + assert_tag_match(posts, "order:chartags_asc") + assert_tag_match(posts, "order:copytags_asc") + assert_tag_match(posts, "order:note_count_asc") + assert_tag_match(posts, "order:notes_asc") + end + + should "return posts for order:comment_bumped" do + post1 = create(:post) + post2 = create(:post) + post3 = create(:post) + user = create(:gold_user) + + as(user) do + comment1 = create(:comment, creator: user, post: post1) + comment2 = create(:comment, creator: user, post: post2, do_not_bump_post: true) + comment3 = create(:comment, creator: user, 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 = 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 = create(:post, file_size: 1.megabyte) + + assert_tag_match([], "filesize:1048000b") + assert_tag_match([], "filesize:1048000") + end + + should "resolve aliases to the actual tag" do + create(:tag_alias, antecedent_name: "kitten", consequent_name: "cat") + post1 = create(:post, tag_string: "cat") + post2 = create(:post, tag_string: "dog") + + assert_tag_match([post1], "kitten") + assert_tag_match([post2], "-kitten") + end + + should "fail for more than 6 tags" do + post1 = 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 "not count free tags against the user's search limit" do + post1 = create(:post, tag_string: "aaa bbb rating:s") + + Danbooru.config.expects(:is_unlimited_tag?).with("rating:s").once.returns(true) + Danbooru.config.expects(:is_unlimited_tag?).with(anything).twice.returns(false) + assert_tag_match([post1], "aaa bbb rating:s") + end + + should "succeed for exclusive tag searches with no other tag" do + post1 = 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 = create(:post, rating: "s", tag_string: "aaa") + assert_nothing_raised do + relation = Post.tag_match("-aaa id:>0") + end + end + end +end diff --git a/test/unit/post_test.rb b/test/unit/post_test.rb index d824c7ce5..28d7f3262 100644 --- a/test/unit/post_test.rb +++ b/test/unit/post_test.rb @@ -1,10 +1,6 @@ require 'test_helper' class PostTest < ActiveSupport::TestCase - def assert_tag_match(posts, query) - assert_equal(posts.map(&:id), Post.tag_match(query).pluck(:id)) - end - def self.assert_invalid_tag(tag_name) should "not allow '#{tag_name}' to be tagged" do post = build(:post, tag_string: "touhou #{tag_name}") @@ -1886,759 +1882,6 @@ class PostTest < ActiveSupport::TestCase end end - context "Searching:" do - should "return posts for the age:<1minute tag" do - post = FactoryBot.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 = FactoryBot.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 = FactoryBot.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 = FactoryBot.create(:post, :tag_string => "'") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - - assert_tag_match([post1], "'") - end - - should "return posts for the \\ tag" do - post1 = FactoryBot.create(:post, :tag_string => "\\") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - - assert_tag_match([post1], "\\") - end - - should "return posts for the ( tag" do - post1 = FactoryBot.create(:post, :tag_string => "(") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - - assert_tag_match([post1], "(") - end - - should "return posts for the ? tag" do - post1 = FactoryBot.create(:post, :tag_string => "?") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - - assert_tag_match([post1], "?") - end - - should "return posts for 1 tag" do - post1 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - post3 = FactoryBot.create(:post, :tag_string => "bbb ccc") - - assert_tag_match([post2, post1], "aaa") - end - - should "return posts for a 2 tag join" do - post1 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - post3 = FactoryBot.create(:post, :tag_string => "bbb ccc") - - assert_tag_match([post2], "aaa bbb") - end - - should "return posts for a 2 tag union" do - post1 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaab bbb") - post3 = FactoryBot.create(:post, :tag_string => "bbb ccc") - - assert_tag_match([post3, post1], "~aaa ~ccc") - end - - should "return posts for 1 tag with exclusion" do - post1 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaa bbb") - post3 = FactoryBot.create(:post, :tag_string => "bbb ccc") - - assert_tag_match([post1], "aaa -bbb") - end - - should "return posts for 1 tag with a pattern" do - post1 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaab bbb") - post3 = FactoryBot.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 = FactoryBot.create(:post, :tag_string => "aaa") - post2 = FactoryBot.create(:post, :tag_string => "aaab bbb") - post3 = FactoryBot.create(:post, :tag_string => "bbb ccc") - - assert_tag_match([post2], "a* bbb") - end - - should "return posts for a negated pattern" do - post1 = create(:post, tag_string: "aaa") - post2 = create(:post, tag_string: "aaab bbb") - post3 = create(:post, tag_string: "bbb ccc") - - assert_tag_match([post3], "-a*") - assert_tag_match([post3], "bbb -a*") - assert_tag_match([post3], "~bbb -a*") - assert_tag_match([post1], "a* -*b") - assert_tag_match([post2], "-*c -a*a") - end - - should "ignore invalid operator syntax" do - assert_nothing_raised do - assert_tag_match([], "-") - assert_tag_match([], "~") - end - end - - should "return posts for the id: metatag" do - posts = FactoryBot.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 - user1 = create(:user) - user2 = create(:user) - user3 = create(:user, enable_private_favorites: true) - post1 = as(user1) { create(:post, tag_string: "fav:true") } - post2 = as(user2) { create(:post, tag_string: "fav:true") } - post3 = as(user3) { create(:post, tag_string: "fav:true") } - - assert_tag_match([post1], "fav:#{user1.name}") - assert_tag_match([post2], "fav:#{user2.name}") - assert_tag_match([], "fav:#{user3.name}") - assert_tag_match([], "fav:dne") - - assert_tag_match([post3, post2], "-fav:#{user1.name}") - assert_tag_match([post3, post2, post1], "-fav:dne") - - as(user3) do - assert_tag_match([post3], "fav:#{user3.name}") - assert_tag_match([post2, post1], "-fav:#{user3.name}") - end - end - - should "return posts for the ordfav: metatag" do - post1 = FactoryBot.create(:post, tag_string: "fav:#{CurrentUser.name}") - post2 = FactoryBot.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) - - pool1 = create(:pool, name: "test_a", category: "series") - pool2 = create(:pool, name: "test_b", category: "collection") - post1 = create(:post, tag_string: "pool:test_a") - post2 = create(:post, tag_string: "pool:test_b") - - assert_tag_match([post1], "pool:#{pool1.id}") - assert_tag_match([post2], "pool:#{pool2.id}") - - assert_tag_match([post1], "pool:TEST_A") - assert_tag_match([post2], "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([], "-pool:test_a -pool:test_b") - - assert_tag_match([post2, post1], "pool:test*") - - assert_tag_match([post2, post1], "pool:any") - assert_tag_match([post2, post1], "-pool:none") - assert_tag_match([], "-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 = FactoryBot.create_list(:post, 2, tag_string: "newpool:test") - - assert_tag_match(posts, "ordpool:test") - end - - should "return posts for the ordpool: metatag for a series pool containing duplicate posts" do - posts = FactoryBot.create_list(:post, 2) - pool = FactoryBot.create(:pool, name: "test", category: "series", post_ids: [posts[0].id, posts[1].id, posts[1].id]) - - assert_tag_match([posts[0], posts[1], posts[1]], "ordpool:test") - end - - should "return posts for the parent: metatag" do - parent = FactoryBot.create(:post) - child = FactoryBot.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 - post1 = create(:post) - post2 = create(:post) - post3 = create(:post) - - favgroup1 = create(:favorite_group, creator: CurrentUser.user, post_ids: [post1.id]) - favgroup2 = create(:favorite_group, creator: CurrentUser.user, post_ids: [post2.id]) - favgroup3 = create(:favorite_group, creator: create(:user), post_ids: [post3.id], is_public: false) - - assert_tag_match([post1], "favgroup:#{favgroup1.id}") - assert_tag_match([post2], "favgroup:#{favgroup2.name}") - assert_tag_match([], "favgroup:#{favgroup3.name}") - assert_tag_match([], "favgroup:dne") - - assert_tag_match([post3, post2], "-favgroup:#{favgroup1.id}") - assert_tag_match([post3, post1], "-favgroup:#{favgroup2.name}") - assert_tag_match([post3, post2, post1], "-favgroup:#{favgroup3.name}") - assert_tag_match([post3, post2, post1], "-favgroup:dne") - - assert_tag_match([post3], "-favgroup:#{favgroup1.name} -favgroup:#{favgroup2.name}") - - as(favgroup3.creator) do - assert_tag_match([post1], "favgroup:#{favgroup1.id}") - assert_tag_match([post2], "favgroup:#{favgroup2.id}") - assert_tag_match([post3], "favgroup:#{favgroup3.id}") - - assert_tag_match([], "favgroup:#{favgroup1.name}") - assert_tag_match([], "favgroup:#{favgroup2.name}") - assert_tag_match([post3], "favgroup:#{favgroup3.name}") - end - end - - should "return posts for the user: metatag" do - users = FactoryBot.create_list(:user, 2, created_at: 2.weeks.ago) - posts = users.map { |u| FactoryBot.create(:post, uploader: u) } - - assert_tag_match([posts[0]], "user:#{users[0].name}") - assert_tag_match([posts[1]], "-user:#{users[0].name}") - assert_tag_match([posts[1]], "filetype:jpg -user:#{users[0].name}") - end - - should "return posts for the approver: metatag" do - users = FactoryBot.create_list(:user, 2) - posts = users.map { |u| FactoryBot.create(:post, approver: u) } - posts << FactoryBot.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 = FactoryBot.create_list(:user, 2, created_at: 2.weeks.ago) - posts = FactoryBot.create_list(:post, 2) - comms = users.zip(posts).map { |u, p| as(u) { FactoryBot.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 = FactoryBot.create_list(:post, 2) - create(:comment, creator: create(:user, created_at: 2.weeks.ago), post: posts[0], is_deleted: false) - create(:comment, creator: create(:user, created_at: 2.weeks.ago), post: posts[1], is_deleted: true) - - assert_tag_match(posts.reverse, "commenter:any") - assert_tag_match([], "commenter:none") - end - - should "return posts for the noter: metatag" do - users = FactoryBot.create_list(:user, 2) - posts = FactoryBot.create_list(:post, 2) - notes = users.zip(posts).map do |u, p| - as(u) { create(:note, post: p) } - end - - 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 = FactoryBot.create_list(:post, 2) - FactoryBot.create(:note, post: posts[0], is_active: true) - FactoryBot.create(:note, post: posts[1], is_active: false) - - assert_tag_match(posts.reverse, "noter:any") - assert_tag_match([], "noter:none") - end - - should "return posts for the note_count: metatag" do - posts = FactoryBot.create_list(:post, 3) - FactoryBot.create(:note, post: posts[0], is_active: true) - FactoryBot.create(:note, post: posts[1], is_active: false) - - assert_tag_match([posts[1], posts[0]], "note_count:1") - assert_tag_match([posts[0]], "active_note_count:1") - assert_tag_match([posts[1]], "deleted_note_count:1") - - assert_tag_match([posts[1], posts[0]], "notes:1") - assert_tag_match([posts[0]], "active_notes:1") - assert_tag_match([posts[1]], "deleted_notes:1") - end - - should "return posts for the commentaryupdater: metatag" do - user1 = create(:user) - user2 = create(:user) - post1 = create(:post) - post2 = create(:post) - artcomm1 = as(user1) { create(:artist_commentary, post: post1) } - artcomm2 = as(user2) { create(:artist_commentary, post: post2) } - - assert_tag_match([post1], "commentaryupdater:#{user1.name}") - assert_tag_match([post2], "commentaryupdater:#{user2.name}") - assert_tag_match([post2], "-commentaryupdater:#{user1.name}") - assert_tag_match([post1], "-commentaryupdater:#{user2.name}") - - assert_tag_match([post1], "artcomm:#{user1.name}") - assert_tag_match([post2], "artcomm:#{user2.name}") - assert_tag_match([post2], "-artcomm:#{user1.name}") - assert_tag_match([post1], "-artcomm:#{user2.name}") - - assert_tag_match([post2, post1], "commentaryupdater:any") - assert_tag_match([], "commentaryupdater:none") - end - - should "return posts for the commentary: metatag" do - post1 = create(:post) - post2 = create(:post) - post3 = create(:post) - post4 = create(:post) - - artcomm1 = create(:artist_commentary, post: post1, translated_title: "azur lane") - artcomm2 = create(:artist_commentary, post: post2, translated_title: "", translated_description: "") - artcomm3 = create(:artist_commentary, post: post3, original_title: "", original_description: "", translated_title: "", translated_description: "") - - assert_tag_match([post2, post1], "commentary:true") - assert_tag_match([post4, post3], "commentary:false") - - assert_tag_match([post4, post3], "-commentary:true") - assert_tag_match([post2, post1], "-commentary:false") - - assert_tag_match([post1], "commentary:translated") - assert_tag_match([post4, post3, post2], "-commentary:translated") - - assert_tag_match([post2], "commentary:untranslated") - assert_tag_match([post4, post3, post1], "-commentary:untranslated") - - assert_tag_match([post1], 'commentary:"azur lane"') - assert_tag_match([post4, post3, post2], '-commentary:"azur lane"') - end - - should "return posts for the date: metatag" do - post = FactoryBot.create(:post, created_at: Time.parse("2017-01-01 12:00")) - - assert_tag_match([post], "date:2017-01-01") - end - - should "return posts for the age: metatag" do - post = FactoryBot.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 = FactoryBot.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 = FactoryBot.create(:post, is_pending: true) - flagged = FactoryBot.create(:post, is_flagged: true) - deleted = FactoryBot.create(:post, is_deleted: true) - banned = FactoryBot.create(:post, is_banned: true) - all = [banned, deleted, flagged, pending] - - assert_tag_match([flagged, pending], "status:modqueue") - 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([], "status:active") - assert_tag_match(all, "status:any") - assert_tag_match(all, "status:all") - - assert_tag_match(all - [flagged, pending], "-status:modqueue") - 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, "-status:active") - end - - should "return posts for the status:unmoderated metatag" do - flagged = FactoryBot.create(:post, is_flagged: true) - pending = FactoryBot.create(:post, is_pending: true) - disapproved = FactoryBot.create(:post, is_pending: true) - - create(:post_flag, post: flagged, creator: create(:user, created_at: 2.weeks.ago)) - create(:post_disapproval, user: CurrentUser.user, post: disapproved, reason: "disinterest") - - assert_tag_match([pending, flagged], "status:unmoderated") - end - - should "respect the 'Deleted post filter' option when using the status:banned metatag" do - deleted = FactoryBot.create(:post, is_deleted: true, is_banned: true) - undeleted = FactoryBot.create(:post, is_banned: true) - - CurrentUser.hide_deleted_posts = true - assert_tag_match([undeleted], "status:banned") - - CurrentUser.hide_deleted_posts = false - assert_tag_match([undeleted, deleted], "status:banned") - end - - should "return posts for the filetype: metatag" do - png = FactoryBot.create(:post, file_ext: "png") - jpg = FactoryBot.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 = FactoryBot.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 = FactoryBot.create(:post, :md5 => "abcd") - post2 = FactoryBot.create(:post) - - assert_tag_match([post1], "md5:abcd") - end - - should "return posts for a source search" do - post1 = FactoryBot.create(:post, :source => "abcd") - post2 = FactoryBot.create(:post, :source => "abcdefg") - post3 = FactoryBot.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 = FactoryBot.create(:post, :source => "ABCD") - post2 = FactoryBot.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 = FactoryBot.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/*") - 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 = FactoryBot.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 = FactoryBot.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 = FactoryBot.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 = FactoryBot.create(:post, :source => url) - assert_tag_match([post], "pixiv_id:34551381") - end - - should "return posts for a pixiv_id:any search" do - url = "http://i1.pixiv.net/img-original/img/2014/10/02/13/51/23/46304396_p0.png" - post = FactoryBot.create(:post, source: url) - assert_tag_match([post], "pixiv_id:any") - end - - should "return posts for a pixiv_id:none search" do - post = FactoryBot.create(:post) - assert_tag_match([post], "pixiv_id:none") - end - - context "saved searches" do - setup do - @post1 = FactoryBot.create(:post, tag_string: "aaa") - @post2 = FactoryBot.create(:post, tag_string: "bbb") - FactoryBot.create(:saved_search, query: "aaa", labels: ["zzz"], user: CurrentUser.user) - FactoryBot.create(:saved_search, query: "bbb", user: CurrentUser.user) - end - - context "labeled" do - should "work" do - SavedSearch.expects(:post_ids_for).with(CurrentUser.id, label: "zzz").returns([@post1.id]) - assert_tag_match([@post1], "search:zzz") - end - end - - context "missing" do - should "work" do - SavedSearch.expects(:post_ids_for).with(CurrentUser.id, label: "uncategorized").returns([@post2.id]) - assert_tag_match([@post2], "search:uncategorized") - end - end - - context "all" do - should "work" do - SavedSearch.expects(:post_ids_for).with(CurrentUser.id).returns([@post1.id, @post2.id]) - assert_tag_match([@post2, @post1], "search:all") - end - end - end - - should "return posts for a rating: metatag" do - s = FactoryBot.create(:post, :rating => "s") - q = FactoryBot.create(:post, :rating => "q") - e = FactoryBot.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 = FactoryBot.create(:post, is_rating_locked: true) - note_locked = FactoryBot.create(:post, is_note_locked: true) - status_locked = FactoryBot.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(FactoryBot.create(:mod_user)) do - upvoted = FactoryBot.create(:post, tag_string: "upvote:self") - downvoted = FactoryBot.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 for a disapproved: metatag" do - CurrentUser.scoped(FactoryBot.create(:mod_user)) do - pending = FactoryBot.create(:post, is_pending: true) - disapproved = FactoryBot.create(:post, is_pending: true) - disapproval = FactoryBot.create(:post_disapproval, user: CurrentUser.user, post: disapproved, reason: "disinterest") - - assert_tag_match([disapproved], "disapproved:#{CurrentUser.name}") - assert_tag_match([disapproved], "disapproved:disinterest") - assert_tag_match([], "disapproved:breaks_rules") - - assert_tag_match([pending], "-disapproved:#{CurrentUser.name}") - assert_tag_match([pending], "-disapproved:disinterest") - assert_tag_match([disapproved, pending], "-disapproved:breaks_rules") - end - end - - should "return posts ordered by a particular attribute" do - posts = (1..2).map do |n| - tags = ["tagme", "gentag1 gentag2 artist:arttag char:chartag copy:copytag"] - - p = FactoryBot.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, - tag_string: tags[n - 1] - ) - - u = create(:user, created_at: 2.weeks.ago) - create(:artist_commentary, post: p) - create(:comment, post: p, creator: u, do_not_bump_post: false) - create(:note, post: p) - p - end - - FactoryBot.create(:note, post: posts.second) - - 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:tagcount") - assert_tag_match(posts.reverse, "order:gentags") - assert_tag_match(posts.reverse, "order:arttags") - assert_tag_match(posts.reverse, "order:chartags") - assert_tag_match(posts.reverse, "order:copytags") - assert_tag_match(posts.reverse, "order:rank") - assert_tag_match(posts.reverse, "order:note_count") - assert_tag_match(posts.reverse, "order:note_count_desc") - assert_tag_match(posts.reverse, "order:notes") - assert_tag_match(posts.reverse, "order:notes_desc") - - 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") - assert_tag_match(posts, "order:tagcount_asc") - assert_tag_match(posts, "order:gentags_asc") - assert_tag_match(posts, "order:arttags_asc") - assert_tag_match(posts, "order:chartags_asc") - assert_tag_match(posts, "order:copytags_asc") - assert_tag_match(posts, "order:note_count_asc") - assert_tag_match(posts, "order:notes_asc") - end - - should "return posts for order:comment_bumped" do - post1 = FactoryBot.create(:post) - post2 = FactoryBot.create(:post) - post3 = FactoryBot.create(:post) - user = create(:gold_user) - - as(user) do - comment1 = create(:comment, creator: user, post: post1) - comment2 = create(:comment, creator: user, post: post2, do_not_bump_post: true) - comment3 = create(:comment, creator: user, 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 = FactoryBot.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 = FactoryBot.create(:post, :file_size => 1.megabyte) - - assert_tag_match([], "filesize:1048000b") - assert_tag_match([], "filesize:1048000") - end - - should "resolve aliases to the actual tag" do - create(:tag_alias, antecedent_name: "kitten", consequent_name: "cat") - post1 = create(:post, tag_string: "cat") - post2 = create(:post, tag_string: "dog") - - assert_tag_match([post1], "kitten") - assert_tag_match([post2], "-kitten") - end - - should "fail for more than 6 tags" do - post1 = FactoryBot.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 "not count free tags against the user's search limit" do - post1 = FactoryBot.create(:post, tag_string: "aaa bbb rating:s") - - Danbooru.config.expects(:is_unlimited_tag?).with("rating:s").once.returns(true) - Danbooru.config.expects(:is_unlimited_tag?).with(anything).twice.returns(false) - assert_tag_match([post1], "aaa bbb rating:s") - end - - should "succeed for exclusive tag searches with no other tag" do - post1 = FactoryBot.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 = FactoryBot.create(:post, :rating => "s", :tag_string => "aaa") - assert_nothing_raised do - relation = Post.tag_match("-aaa id:>0") - end - end - end - context "Voting:" do should "not allow members to vote" do @user = FactoryBot.create(:user)