557 lines
20 KiB
Ruby
557 lines
20 KiB
Ruby
require "spec_helper"
|
|
|
|
describe AbuseReport do
|
|
it { is_expected.to validate_presence_of(:url) }
|
|
|
|
context "when report is not spam" do
|
|
context "valid reports" do
|
|
it "is valid" do
|
|
expect(build(:abuse_report)).to be_valid
|
|
end
|
|
end
|
|
|
|
context "comment missing" do
|
|
let(:report_without_comment) { build(:abuse_report, comment: nil) }
|
|
it "is invalid" do
|
|
expect(report_without_comment.save).to be_falsey
|
|
expect(report_without_comment.errors[:comment]).not_to be_empty
|
|
end
|
|
end
|
|
|
|
context "comment with weird characters" do
|
|
it "is valid with slash and dot" do
|
|
expect(build(:abuse_report, comment: "/.")).to be_valid
|
|
end
|
|
it "is valid in other languages" do
|
|
expect(build(:abuse_report, comment: "café")).to be_valid
|
|
end
|
|
it "is valid in other alphabets" do
|
|
expect(build(:abuse_report, comment: "γεια")).to be_valid
|
|
end
|
|
end
|
|
|
|
context "provided email is invalid" do
|
|
BAD_EMAILS.each do |email|
|
|
let(:bad_email) { build(:abuse_report, email: email) }
|
|
it "fails email format check and cannot be created" do
|
|
expect(bad_email.save).to be_falsey
|
|
expect(bad_email.errors[:email]).to include("should look like an email address.")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a chapter URL that's missing the work id" do
|
|
context "when the chapter exists" do
|
|
let(:work) { create(:work) }
|
|
let(:chapter) { work.chapters.first }
|
|
let(:missing_work_id) { build(:abuse_report, url: "http://archiveofourown.org/chapters/#{chapter.id}/") }
|
|
|
|
it "saves and adds the correct work id to the URL" do
|
|
expect(missing_work_id.save).to be_truthy
|
|
expect(missing_work_id.url).to eq("http://archiveofourown.org/works/#{work.id}/chapters/#{chapter.id}/")
|
|
end
|
|
|
|
context "when the URL does not include the scheme" do
|
|
let(:missing_work_id) { build(:abuse_report, url: "archiveofourown.org/chapters/#{chapter.id}/") }
|
|
|
|
it "saves and adds a scheme and correct work id to the URL" do
|
|
expect(missing_work_id.save).to be_truthy
|
|
expect(missing_work_id.url).to eq("https://archiveofourown.org/works/#{work.id}/chapters/#{chapter.id}/")
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when the chapter does not exist" do
|
|
let(:chapter_url) { "http://archiveofourown.org/chapters/000" }
|
|
let(:missing_work_id) { build(:abuse_report, url: chapter_url) }
|
|
|
|
it "saves without adding a work id to the URL" do
|
|
expect(missing_work_id.save).to be_truthy
|
|
expect(missing_work_id.url).to eq("#{chapter_url}/")
|
|
end
|
|
|
|
context "when the URL does not include the scheme" do
|
|
let(:chapter_url) { "archiveofourown.org/chapters/000" }
|
|
let(:missing_work_id) { build(:abuse_report, url: chapter_url) }
|
|
|
|
it "saves and adds a scheme but no work id to the URL" do
|
|
expect(missing_work_id.save).to be_truthy
|
|
expect(missing_work_id.url).to eq("https://#{chapter_url}/")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a very long URL" do
|
|
let(:long_url) { "https://archiveofourown.org/#{'a' * 2080}" }
|
|
let(:abuse_report) { build(:abuse_report, url: long_url) }
|
|
|
|
it "truncates the url to the maximum length" do
|
|
expect(abuse_report.save).to be_truthy
|
|
expect(abuse_report.url).to eq(long_url[0..2079])
|
|
end
|
|
end
|
|
|
|
context "invalid url" do
|
|
let(:invalid_url) { build(:abuse_report, url: "nothing before #{ArchiveConfig.APP_URL}") }
|
|
it "text before url" do
|
|
expect(invalid_url.save).to be_falsey
|
|
expect(invalid_url.errors[:url]).not_to be_empty
|
|
end
|
|
|
|
let(:not_from_site) { build(:abuse_report, url: "http://www.google.com/not/our/site") }
|
|
it "url not from our site" do
|
|
expect(not_from_site.save).to be_falsey
|
|
expect(not_from_site.errors[:url]).not_to be_empty
|
|
end
|
|
|
|
let(:no_url) { build(:abuse_report, url: "") }
|
|
it "no url" do
|
|
expect(no_url.save).to be_falsey
|
|
expect(no_url.errors[:url]).not_to be_empty
|
|
end
|
|
end
|
|
|
|
let(:no_email_provided) { build(:abuse_report, email: nil) }
|
|
it "is invalid if an email is not provided" do
|
|
expect(no_email_provided.save).to be_falsey
|
|
expect(no_email_provided.errors[:email]).not_to be_empty
|
|
end
|
|
|
|
let(:email_provided) { build(:abuse_report) }
|
|
it "is valid if an email is provided" do
|
|
expect(email_provided.save).to be_truthy
|
|
expect(email_provided.errors[:email]).to be_empty
|
|
end
|
|
|
|
context "for an already-reported work" do
|
|
work_url = "http://archiveofourown.org/works/1234"
|
|
|
|
let(:common_report) { build(:abuse_report, url: work_url) }
|
|
it "can be submitted up to a set number of times" do
|
|
(ArchiveConfig.ABUSE_REPORTS_PER_WORK_MAX - 1).times do
|
|
create(:abuse_report, url: work_url)
|
|
end
|
|
expect(common_report.save).to be_truthy
|
|
expect(common_report.errors[:base]).to be_empty
|
|
end
|
|
end
|
|
|
|
shared_examples "enough already" do |url|
|
|
let(:report) { build(:abuse_report, url: url) }
|
|
it "can't be submitted" do
|
|
expect(report.save).to be_falsey
|
|
expect(report.errors[:base].first).to include("This page has already been reported.")
|
|
end
|
|
end
|
|
|
|
shared_examples "alright" do |url|
|
|
let(:report) { build(:abuse_report, url: url) }
|
|
it "can be submitted" do
|
|
expect(report.save).to be_truthy
|
|
expect(report.errors[:base]).to be_empty
|
|
end
|
|
end
|
|
|
|
context "for a work reported the maximum number of times" do
|
|
work_url = "http://archiveofourown.org/works/789"
|
|
|
|
before do
|
|
ArchiveConfig.ABUSE_REPORTS_PER_WORK_MAX.times do
|
|
create(:abuse_report, url: work_url)
|
|
end
|
|
expect(AbuseReport.count).to eq(ArchiveConfig.ABUSE_REPORTS_PER_WORK_MAX)
|
|
end
|
|
|
|
# obviously
|
|
it_behaves_like "enough already", work_url
|
|
|
|
# the same work, different protocol
|
|
it_behaves_like "enough already", "https://archiveofourown.org/works/789"
|
|
|
|
# the same work, with parameters/anchors
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789?smut=yes"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789?smut=yes#timeline"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789#timeline"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/?smut=yes"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/#timeline"
|
|
|
|
# the same work, in a collection
|
|
it_behaves_like "enough already", "http://archiveofourown.org/collections/rarepair/works/789"
|
|
|
|
# the same work, under users
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/author/works/789"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/coauthor/works/789"
|
|
|
|
# the same work, subpages
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/bookmarks"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/collections"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/comments"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/kudos"
|
|
|
|
# a specific chapter on the work
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/chapters/123"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/chapters/123#major-character-death"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/chapters/123?ending=1"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/works/789/chapters/123?ending=2#major-character-death"
|
|
|
|
# the same work: variations we don't cover
|
|
it_behaves_like "alright", "http://archiveofourown.org/chapters/123"
|
|
it_behaves_like "alright", "http://archiveofourown.org/comments/show_comments?work_id=789"
|
|
|
|
# not the same work
|
|
it_behaves_like "alright", "http://archiveofourown.org/works/9009"
|
|
it_behaves_like "alright", "http://archiveofourown.org/works/78"
|
|
it_behaves_like "alright", "http://archiveofourown.org/works/7890"
|
|
it_behaves_like "alright", "http://archiveofourown.org/external_works/789"
|
|
|
|
# unrelated
|
|
it_behaves_like "alright", "http://archiveofourown.org/users/someone"
|
|
|
|
context "a month later" do
|
|
before { travel(32.days) }
|
|
|
|
it_behaves_like "alright", work_url
|
|
end
|
|
end
|
|
|
|
context "for a user profile reported the maximum number of times" do
|
|
user_url = "http://archiveofourown.org/users/someone"
|
|
|
|
before do
|
|
ArchiveConfig.ABUSE_REPORTS_PER_USER_MAX.times do
|
|
create(:abuse_report, url: user_url)
|
|
end
|
|
expect(AbuseReport.count).to eq(ArchiveConfig.ABUSE_REPORTS_PER_USER_MAX)
|
|
end
|
|
|
|
# obviously
|
|
it_behaves_like "enough already", user_url
|
|
|
|
# the same user, different protocol
|
|
it_behaves_like "enough already", "https://archiveofourown.org/users/someone"
|
|
|
|
# the same user, with parameters/anchors
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone?sfw=yes"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone?sfw=yes#timeline"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone#timeline"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/?sfw=yes"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/#timeline"
|
|
|
|
# the same user, as admin (why admin?)
|
|
it_behaves_like "enough already", "http://archiveofourown.org/admin/users/someone"
|
|
|
|
# the same user, subpages
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/bookmarks"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/claims"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/pseuds/"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/pseuds/ghostwriter"
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/pseuds/g h o s t w r i t e r"
|
|
|
|
# the same user, Unicode in parameters
|
|
it_behaves_like "enough already", "http://archiveofourown.org/users/someone/inbox?utf8=✓&filters[read]=false"
|
|
|
|
# not the same user
|
|
it_behaves_like "alright", "http://archiveofourown.org/users/some"
|
|
it_behaves_like "alright", "http://archiveofourown.org/users/someoneelse"
|
|
it_behaves_like "alright", "http://archiveofourown.org/users/somebody"
|
|
|
|
# unrelated
|
|
it_behaves_like "alright", "http://archiveofourown.org/works/789"
|
|
|
|
context "a month later" do
|
|
before { travel(32.days) }
|
|
|
|
it_behaves_like "alright", user_url
|
|
end
|
|
end
|
|
|
|
context "for a URL that is not a work" do
|
|
page_url = "http://archiveofourown.org/tags/Testing/works"
|
|
|
|
let(:common_report) { build(:abuse_report, url: page_url) }
|
|
it "can be submitted an unrestricted number of times" do
|
|
ArchiveConfig.ABUSE_REPORTS_PER_WORK_MAX.times do
|
|
create(:abuse_report, url: page_url)
|
|
end
|
|
expect(common_report.save).to be_truthy
|
|
expect(common_report.errors[:base]).to be_empty
|
|
end
|
|
end
|
|
|
|
context "for alternate URL format" do
|
|
let(:report) { build(:abuse_report) }
|
|
|
|
it "no protocol" do
|
|
report.url = "archiveofourown.org"
|
|
expect(report.valid?).to be_truthy
|
|
end
|
|
|
|
it "dot com" do
|
|
report.url = "http://archiveofourown.com"
|
|
expect(report.valid?).to be_truthy
|
|
end
|
|
|
|
it "acronym" do
|
|
report.url = "http://ao3.org"
|
|
expect(report.valid?).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "when email is valid" do
|
|
let(:report) { build(:abuse_report, email: "email@example.com") }
|
|
|
|
context "when email has submitted less than the maximum daily number of reports" do
|
|
before do
|
|
(ArchiveConfig.ABUSE_REPORTS_PER_EMAIL_MAX - 1).times do
|
|
create(:abuse_report, email: "email@example.com")
|
|
end
|
|
end
|
|
|
|
it "can be submitted" do
|
|
expect(report.save).to be_truthy
|
|
expect(report.errors[:base]).to be_empty
|
|
end
|
|
end
|
|
|
|
context "when email has submitted the maximum daily number of reports" do
|
|
before do
|
|
ArchiveConfig.ABUSE_REPORTS_PER_EMAIL_MAX.times do
|
|
create(:abuse_report, email: "email@example.com")
|
|
end
|
|
end
|
|
|
|
it "can't be submitted" do
|
|
expect(report.save).to be_falsey
|
|
expect(report.errors[:base].first).to include("daily reporting limit")
|
|
end
|
|
|
|
context "when it's a day later" do
|
|
before { travel(1.day) }
|
|
|
|
it "can be submitted" do
|
|
expect(report.save).to be_truthy
|
|
expect(report.errors[:base]).to be_empty
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "when report is spam" do
|
|
let(:legit_user) { create(:user) }
|
|
let(:spam_report) { build(:abuse_report, username: "viagra-test-123") }
|
|
let!(:safe_report) { build(:abuse_report, username: "viagra-test-123", email: legit_user.email) }
|
|
|
|
before do
|
|
allow(Akismetor).to receive(:spam?).and_return(true)
|
|
end
|
|
|
|
it "is not valid if Akismet flags it as spam" do
|
|
expect(spam_report.save).to be_falsey
|
|
expect(spam_report.errors[:base]).to include("This report looks like spam to our system!")
|
|
end
|
|
|
|
it "is valid even if the email casing is different" do
|
|
legit_user.email = legit_user.email.upcase
|
|
legit_user.save
|
|
User.current_user = legit_user
|
|
expect(safe_report.save).to be_truthy
|
|
end
|
|
|
|
it "is valid even with spam if logged in and providing correct email" do
|
|
User.current_user = legit_user
|
|
expect(safe_report.save).to be_truthy
|
|
end
|
|
end
|
|
|
|
context "when report is submitted to Akismet" do
|
|
let(:report) { build(:abuse_report) }
|
|
|
|
it "has comment_type \"contact-form\"" do
|
|
expect(report.akismet_attributes[:comment_type]).to eq("contact-form")
|
|
end
|
|
|
|
it "has user_role \"user-with-nonmatching-email\" when reporter is logged in" do
|
|
User.current_user = create(:user)
|
|
expect(report.akismet_attributes[:user_role]).to eq("user-with-nonmatching-email")
|
|
end
|
|
|
|
it "has user_role \"guest\" when reporter is logged out" do
|
|
expect(report.akismet_attributes[:user_role]).to eq("guest")
|
|
end
|
|
end
|
|
|
|
describe "#attach_work_download" do
|
|
include ActiveJob::TestHelper
|
|
def queue_adapter_for_test
|
|
ActiveJob::QueueAdapters::TestAdapter.new
|
|
end
|
|
|
|
let(:ticket_id) { "123" }
|
|
let(:work) { create(:work) }
|
|
|
|
it "does not attach a download for non-work URLs asynchronously" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/users/someone/")
|
|
|
|
expect { subject.attach_work_download(ticket_id) }
|
|
.not_to have_enqueued_job
|
|
end
|
|
|
|
it "does not attach a download for comment sub-URLs asynchronously" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/comments/")
|
|
|
|
expect { subject.attach_work_download(ticket_id) }
|
|
.not_to have_enqueued_job
|
|
end
|
|
|
|
it "attaches a download for work URLs asynchronously" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect { subject.attach_work_download(ticket_id) }
|
|
.to have_enqueued_job
|
|
end
|
|
end
|
|
|
|
describe "#creator_ids" do
|
|
it "returns no creator ids for non-work URLs" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/users/someone/")
|
|
|
|
expect(subject.creator_ids).to be_nil
|
|
end
|
|
|
|
it "returns no creator ids for comment sub-URLs" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/123/comments/")
|
|
|
|
expect(subject.creator_ids).to be_nil
|
|
end
|
|
|
|
context "for work URLs" do
|
|
it "returns deletedwork for a work that doesn't exist" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/000/")
|
|
|
|
expect(subject.creator_ids).to eq("deletedwork")
|
|
end
|
|
|
|
context "for a single creator" do
|
|
let(:work) { create(:work) }
|
|
|
|
it "returns a single creator id" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq(work.users.first.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "for an anonymous work" do
|
|
let(:anonymous_collection) { create(:anonymous_collection) }
|
|
let(:work) { create(:work, collections: [anonymous_collection]) }
|
|
|
|
it "returns a single creator id" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq(work.users.first.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "for an unrevealed work" do
|
|
let(:unrevealed_collection) { create(:unrevealed_collection) }
|
|
let(:work) { create(:work, collections: [unrevealed_collection]) }
|
|
|
|
it "returns a single creator id" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq(work.users.first.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "for multiple pseuds of one creator" do
|
|
let(:user) { create(:user) }
|
|
let(:pseud) { create(:pseud, user: user) }
|
|
let(:work) { create(:work, authors: [pseud, user.default_pseud]) }
|
|
|
|
it "returns a single creator id" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq(user.id.to_s)
|
|
end
|
|
end
|
|
|
|
context "for multiple creators" do
|
|
let(:user1) { create(:user, id: 10) }
|
|
let(:user2) { create(:user, id: 11) }
|
|
let(:work) { create(:work, authors: [user2.default_pseud, user1.default_pseud]) }
|
|
|
|
it "returns a sorted list of creator ids" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq("#{user1.id}, #{user2.id}")
|
|
end
|
|
end
|
|
|
|
context "for an invited co-creator that hasn't accepted yet" do
|
|
let(:user) { create(:user) }
|
|
let(:invited) { create(:user) }
|
|
let(:work) { create(:work, authors: [user.default_pseud, invited.default_pseud]) }
|
|
let(:creatorship) { work.creatorships.last }
|
|
|
|
before do
|
|
creatorship.approved = false
|
|
creatorship.save!(validate: false)
|
|
end
|
|
|
|
it "returns only the creator" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq(user.id.to_s)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "for an orphaned work" do
|
|
let!(:orphan_account) { create(:user, login: "orphan_account") }
|
|
let(:orphaneer) { create(:user, id: 40) }
|
|
let(:work) { create(:work, authors: [orphaneer.default_pseud]) }
|
|
|
|
context "recently orphaned" do
|
|
before do
|
|
Creatorship.orphan([orphaneer.default_pseud], [work], false)
|
|
end
|
|
|
|
it "returns orphanedwork and the original creator" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq("orphanedwork, #{orphaneer.id}")
|
|
end
|
|
end
|
|
|
|
context "orphaned a long time ago" do
|
|
before do
|
|
Creatorship.orphan([orphaneer.default_pseud], [work], false)
|
|
work.original_creators.destroy_all
|
|
end
|
|
|
|
it "returns orphanedwork" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq("orphanedwork")
|
|
end
|
|
end
|
|
|
|
context "partially orphaned" do
|
|
let(:cocreator) { create(:user, id: 41) }
|
|
let(:work) { create(:work, authors: [cocreator.default_pseud, orphaneer.default_pseud]) }
|
|
|
|
before do
|
|
Creatorship.orphan([orphaneer.default_pseud], [work], false)
|
|
end
|
|
|
|
it "returns a sorted list of orphanedwork, the co-creator and the original creator" do
|
|
allow(subject).to receive(:url).and_return("http://archiveofourown.org/works/#{work.id}/")
|
|
|
|
expect(subject.creator_ids).to eq("orphanedwork, #{orphaneer.id}, #{cocreator.id}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|