1233 lines
37 KiB
Ruby
1233 lines
37 KiB
Ruby
# typed: false
|
|
require "rails_helper"
|
|
|
|
describe Domain::Fa::Job::UserPageJob do
|
|
let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
|
|
before do
|
|
Scraper::ClientFactory.http_client_mock = http_client_mock
|
|
@log_entries =
|
|
HttpClientMockHelpers.init_http_client_mock(
|
|
http_client_mock,
|
|
client_mock_config,
|
|
)
|
|
end
|
|
|
|
context "the user page has a user with a url name that has brackets" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/thesteamlemur/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_thesteamlemur.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "creates the user" do
|
|
expect do perform_now({ url_name: "thesteamlemur" }) end.to change {
|
|
Domain::User::FaUser.find_by(url_name: "thesteamlemur")
|
|
}.from(nil).to(be_present)
|
|
end
|
|
|
|
it "creates the recent watchers" do
|
|
perform_now({ url_name: "thesteamlemur" })
|
|
user = Domain::User::FaUser.find_by(url_name: "thesteamlemur")
|
|
expect(user.followed_by_users.count).to eq(7)
|
|
expect(user.followed_by_users.map(&:url_name)).to include(
|
|
"[sic]",
|
|
"drakenbyte",
|
|
"naynay",
|
|
)
|
|
end
|
|
end
|
|
|
|
context "the user is disabled" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/christianallree/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_christianallree_account_disabled.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "marks the user as disabled" do
|
|
perform_now({ url_name: "christianallree" })
|
|
user = Domain::User::FaUser.find_by(url_name: "christianallree")
|
|
expect(user.state).to eq("account_disabled")
|
|
end
|
|
end
|
|
|
|
context "scanning a normal user" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/meesh/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Apr 7, 2023 18:26 UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_meesh.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { Domain::User::FaUser.find_by(url_name: "meesh") }
|
|
|
|
let(:faved_post_fa_ids) do
|
|
[
|
|
51_671_587,
|
|
51_660_483,
|
|
51_656_085,
|
|
51_656_135,
|
|
51_633_080,
|
|
51_633_041,
|
|
51_535_939,
|
|
51_631_696,
|
|
51_634_520,
|
|
51_632_374,
|
|
51_631_377,
|
|
51_631_461,
|
|
51_624_859,
|
|
51_618_150,
|
|
51_621_410,
|
|
51_618_586,
|
|
51_617_113,
|
|
51_613_740,
|
|
51_600_523,
|
|
51_594_821,
|
|
]
|
|
end
|
|
|
|
it "records the right stats" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(user).to_not be_nil
|
|
expect(user.num_pageviews).to eq(3_061_083)
|
|
expect(user.num_submissions).to eq(1590)
|
|
expect(user.num_favorites).to eq(1_422_886)
|
|
expect(user.num_comments_recieved).to eq(47_931)
|
|
expect(user.num_comments_given).to eq(17_741)
|
|
expect(user.num_journals).to eq(5)
|
|
expect(user.account_status).to eq("active")
|
|
expect(user.registered_at).to eq(Time.parse("Dec 11, 2005 10:28 -08:00"))
|
|
end
|
|
|
|
it "enqueues a favs job scan" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob)).to match(
|
|
[hash_including(user:, caused_by_entry: @log_entries[0])],
|
|
)
|
|
end
|
|
|
|
it "records favs with fav_id and explicit_time" do
|
|
user = create(:domain_user_fa_user, url_name: "meesh")
|
|
post = create(:domain_post_fa_post, fa_id: 51_617_113)
|
|
|
|
# existing record that has a fav_fa_id but no date
|
|
user.update_fav_model(post_id: post.id, fav_id: 1_234_567_890)
|
|
|
|
perform_now({ url_name: "meesh" })
|
|
user_post_favs = user.user_post_favs
|
|
expect(user_post_favs.count).to eq(20)
|
|
expect(user_post_favs.map(&:post).map(&:fa_id)).to contain_exactly(
|
|
*faved_post_fa_ids,
|
|
)
|
|
fav_51671587 = user_post_favs.find { |f| f.post.fa_id == 51_671_587 }
|
|
expect(fav_51671587.user).to eq(user)
|
|
expect(fav_51671587.explicit_time).to eq(
|
|
Time.parse("Apr 6, 2023 04:28 PM -07:00"),
|
|
)
|
|
expect(fav_51671587.fav_id).to be_nil
|
|
|
|
fav_51617113 = user_post_favs.find { |f| f.post.fa_id == 51_617_113 }
|
|
expect(fav_51617113.user).to eq(user)
|
|
expect(fav_51617113.explicit_time).to eq(
|
|
Time.parse("Apr 2, 2023 02:49 PM -07:00"),
|
|
)
|
|
expect(fav_51617113.fav_id).to eq(1_234_567_890)
|
|
end
|
|
|
|
context "the user does not yet exist" do
|
|
it "the user is created" do
|
|
expect do perform_now({ url_name: "meesh" }) end.to change {
|
|
Domain::User::FaUser.find_by(url_name: "meesh")
|
|
}.from(nil).to(be_present)
|
|
end
|
|
|
|
it "enqueues a user avatar job" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(user).to_not be_nil
|
|
avatar = user.avatar
|
|
expect(avatar).to_not be_nil
|
|
expect(avatar.url_str).to eq(
|
|
"https://a.furaffinity.net/1635789297/meesh.gif",
|
|
)
|
|
expect(avatar.state).to eq("pending")
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserAvatarJob),
|
|
).to match([hash_including(avatar:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
|
|
it "enqueues a gallery job" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(user).to_not be_nil
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
end
|
|
|
|
context "the user exists" do
|
|
let!(:user) { create(:domain_user_fa_user, url_name: "meesh") }
|
|
context "gallery scan was recently performed" do
|
|
before do
|
|
user.scanned_gallery_at = 1.day.ago
|
|
user.save!
|
|
end
|
|
it "does not enqueue a gallery job" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to be_empty
|
|
end
|
|
end
|
|
|
|
context "the gallery scan was not recently performed" do
|
|
before do
|
|
user.scanned_gallery_at = 10.years.ago
|
|
user.save!
|
|
end
|
|
it "enqueues a gallery job" do
|
|
perform_now({ url_name: "meesh" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "all watched users fit in the recently watched section" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/llllvi/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 27, 2025 04:10 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_llllvi_few_watched_users.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { Domain::User::FaUser.find_by(url_name: "llllvi") }
|
|
|
|
it "does not enqueue a follows job" do
|
|
perform_now({ url_name: "llllvi" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserFollowsJob),
|
|
).to be_empty
|
|
end
|
|
|
|
it "adds watched users to the user's followed_users" do
|
|
perform_now({ url_name: "llllvi" })
|
|
expect(user.followed_users.count).to eq(6)
|
|
expect(user.followed_users.map(&:url_name)).to match_array(
|
|
%w[
|
|
koul
|
|
artii
|
|
aquadragon35
|
|
incredibleediblecalico
|
|
nummynumz
|
|
fidchellvore
|
|
],
|
|
)
|
|
end
|
|
|
|
it "marks scanned_follows_at as recent" do
|
|
perform_now({ url_name: "llllvi" })
|
|
expect(user.scanned_follows_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
|
|
it "does not add any users to followed_by_users" do
|
|
perform_now({ url_name: "llllvi" })
|
|
expect(user.followed_by_users.count).to eq(0)
|
|
end
|
|
|
|
it "works when the user already has some followed users" do
|
|
user = create(:domain_user_fa_user, url_name: "llllvi")
|
|
followed_user = create(:domain_user_fa_user, url_name: "koul")
|
|
user.followed_users << followed_user
|
|
perform_now({ url_name: "llllvi" })
|
|
expect(user.followed_users.count).to eq(6)
|
|
expect(user.followed_by_users.count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "the user has no submissions" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/sealingthedeal/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 27, 2025 02:48 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_sealingthedeal_no_submissions.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "records the right number of submissions" do
|
|
perform_now({ url_name: "sealingthedeal" })
|
|
user = Domain::User::FaUser.find_by(url_name: "sealingthedeal")
|
|
expect(user).to_not be_nil
|
|
expect(user.num_submissions).to eq(0)
|
|
end
|
|
|
|
it "does not enqueue a gallery job" do
|
|
perform_now({ url_name: "sealingthedeal" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to be_empty
|
|
end
|
|
end
|
|
|
|
context "the user has a single scrap submission and few watchers" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/zzreg/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 27, 2025 03:56 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_zzreg_one_scrap_submission.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { Domain::User::FaUser.find_by(url_name: "zzreg") }
|
|
|
|
it "records the right number of submissions" do
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(user.num_submissions).to eq(1)
|
|
end
|
|
|
|
it "enqueues a gallery job" do
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
|
|
it "adds watchers to followed_by_users" do
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(user.followed_by_users.count).to eq(5)
|
|
expect(user.followed_by_users.map(&:url_name)).to match_array(
|
|
%w[noneedtothankme karenpls azureparagon zarmir iginger],
|
|
)
|
|
end
|
|
|
|
it "works when the user already has some followed_by_users" do
|
|
user = create(:domain_user_fa_user, url_name: "zzreg")
|
|
followed_by_user =
|
|
create(:domain_user_fa_user, url_name: "noneedtothankme")
|
|
user.followed_by_users << followed_by_user
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(user.followed_by_users.count).to eq(5)
|
|
expect(user.followed_users.count).to eq(0)
|
|
end
|
|
|
|
it "marks scanned_followed_by_at as recent" do
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(user.scanned_followed_by_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
|
|
it "does not mark scanned_follows_at as recent" do
|
|
perform_now({ url_name: "zzreg" })
|
|
expect(user.scanned_follows_at).to be_nil
|
|
end
|
|
end
|
|
|
|
context "the user has more than threshold watched users and they are all known" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/koul/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Mar 1, 2025 08:27 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
# this user page indicates:
|
|
# - 14 watched
|
|
# - 25 watchers
|
|
"domain/fa/user_page/user_page_koul_over_threshold_watchers.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { create(:domain_user_fa_user, url_name: "koul") }
|
|
let(:recent_watched_user_url_names) do
|
|
%w[
|
|
spiritinchoco
|
|
syrrvya
|
|
knyazkolosok
|
|
waspsalad
|
|
moth-sprout
|
|
suzamuri
|
|
2d10
|
|
lemithecat
|
|
fr95
|
|
floki.midnight
|
|
dizzy.
|
|
kilobytefox
|
|
]
|
|
end
|
|
|
|
it "does not mark scanned_follows_at if no followed users are known" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.not_to change { user.scanned_follows_at }
|
|
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserFollowsJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
|
|
it "does not mark scanned_follows_at if page indicates more followed than are recorded" do
|
|
# all in recent are marked as followed
|
|
recent_watched_user_url_names.each do |url_name|
|
|
user.followed_users << create(
|
|
:domain_user_fa_user,
|
|
url_name:,
|
|
name: url_name,
|
|
)
|
|
end
|
|
|
|
expect(user.followed_users.count).to eq(12)
|
|
|
|
# but the page indicates user watching 14
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.not_to change { user.scanned_follows_at }
|
|
|
|
expect(user.followed_users.count).to eq(12)
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserFollowsJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
|
|
it "marks scanned_follows_at as recent if all followed users are known and number of followed matches page" do
|
|
recent_watched_user_url_names.each do |url_name|
|
|
user.followed_users << create(:domain_user_fa_user, url_name:)
|
|
end
|
|
|
|
user.followed_users << create(:domain_user_fa_user, url_name: "test1")
|
|
user.followed_users << create(:domain_user_fa_user, url_name: "test2")
|
|
expect(user.followed_users.count).to eq(14)
|
|
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.scanned_follows_at }.to be_within(3.seconds).of(
|
|
Time.now,
|
|
)
|
|
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserFollowsJob),
|
|
).to be_empty
|
|
end
|
|
end
|
|
|
|
context "the user is watched by more users than the threshold" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/koul/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Mar 1, 2025 08:27 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_koul_over_threshold_watchers.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:watched_by_user_url_names) do
|
|
%w[
|
|
skifmutt
|
|
k92
|
|
erri49
|
|
spaghetti779
|
|
nonuri
|
|
mkyosh
|
|
mystpaww
|
|
retrorinx
|
|
aurorethedire
|
|
rasssss
|
|
coyotesolo
|
|
commissarisador
|
|
]
|
|
end
|
|
|
|
let(:user) { create(:domain_user_fa_user, url_name: "koul") }
|
|
|
|
it "does not mark scanned_followed_by_at as recent if over threshold" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.not_to change { user.scanned_followed_by_at }
|
|
end
|
|
|
|
it "marks scanned_followed_by_at as recent if all watched by users are known and number of watched by matches page" do
|
|
watched_by_user_url_names.each do |url_name|
|
|
user.followed_by_users << create(:domain_user_fa_user, url_name:)
|
|
end
|
|
|
|
# 25 recorded, so make 25-12 more
|
|
(25 - 12).times do |idx|
|
|
user.followed_by_users << create(
|
|
:domain_user_fa_user,
|
|
url_name: "test#{idx}",
|
|
)
|
|
end
|
|
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.scanned_followed_by_at }.to be_within(3.seconds).of(
|
|
Time.now,
|
|
)
|
|
end
|
|
end
|
|
|
|
context "a user with recent gallery submissions" do
|
|
let(:user) { create(:domain_user_fa_user, url_name: "kutua") }
|
|
|
|
context "has one recent gallery submission, and user page indicates three total submissions" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/kutua/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Mar 2, 2025 09:59 AM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
# one recent gallery submission, and three submissions indicated on user page
|
|
"domain/fa/user_page/user_page_kutua_one_recent_three_total_gallery.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "updates the num_submissions count" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.num_submissions }.from(nil).to(3)
|
|
end
|
|
|
|
context "no submissions are yet known" do
|
|
it "creates the recent submission" do
|
|
perform_now({ user: })
|
|
expect(user.posts.count).to eq(1)
|
|
post = user.posts.first
|
|
expect(post.fa_id).to eq(60_073_062)
|
|
end
|
|
|
|
it "does not mark the gallery as scanned" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.not_to change { user.scanned_gallery_at }
|
|
end
|
|
|
|
it "enqueues a ScanPostJob for the recent submission" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match(
|
|
[
|
|
hash_including(
|
|
post: user.posts.first,
|
|
caused_by_entry: @log_entries[0],
|
|
),
|
|
],
|
|
)
|
|
end
|
|
|
|
it "enqueues a UserGalleryJob for the user" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to match([hash_including(user:, caused_by_entry: @log_entries[0])])
|
|
end
|
|
end
|
|
|
|
context "one unlisted submission is known" do
|
|
let!(:unseen_post_1) do
|
|
create(
|
|
:domain_post_fa_post,
|
|
fa_id: 12_345,
|
|
creator: user,
|
|
title: "Not In The Gallery",
|
|
)
|
|
end
|
|
|
|
shared_examples "unlisted submission in state" do |state|
|
|
context "and is in the '#{state}' state" do
|
|
before do
|
|
unseen_post_1.state = state
|
|
unseen_post_1.save!
|
|
end
|
|
|
|
it "enqueues a ScanPostJob only for the new gallery submission" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match(
|
|
[
|
|
hash_including(
|
|
post: user.posts.find_by(fa_id: 60_073_062),
|
|
caused_by_entry: @log_entries[0],
|
|
),
|
|
],
|
|
)
|
|
end
|
|
|
|
it "enqueues a UserGalleryJob for the user" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserGalleryJob),
|
|
).to match(
|
|
[hash_including(user:, caused_by_entry: @log_entries[0])],
|
|
)
|
|
end
|
|
|
|
it "does not mark the gallery as scanned" do
|
|
perform_now({ user: })
|
|
user.reload
|
|
expect(user.scanned_gallery_at).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
# we do not know anything about the unseen submissions, so do
|
|
# not re-enqueue them - only enqueue those in recent gallery section
|
|
Domain::Post::FaPost.states.keys.each do |state|
|
|
include_examples "unlisted submission in state", state
|
|
end
|
|
end
|
|
|
|
context "all two unlisted submissions are known" do
|
|
let!(:unseen_post_1) do
|
|
create(
|
|
:domain_post_fa_post,
|
|
fa_id: 12_345,
|
|
creator: user,
|
|
title: "Not In The Gallery 1",
|
|
)
|
|
end
|
|
|
|
let!(:unseen_post_2) do
|
|
create(
|
|
:domain_post_fa_post,
|
|
fa_id: 12_346,
|
|
creator: user,
|
|
title: "Not In The Gallery 2",
|
|
)
|
|
end
|
|
|
|
it "enqueues a ScanPostJob for the recent submission" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match(
|
|
[hash_including(post: user.posts.find_by(fa_id: 60_073_062))],
|
|
)
|
|
end
|
|
|
|
it "marks the gallery as scanned" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.scanned_gallery_at }.to be_within(3.seconds).of(
|
|
Time.now,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
|
|
context "has one recent gallery submission, and user page indicates one total submission" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
# one recent gallery submission, and one submission indicated on user page
|
|
uri: "https://www.furaffinity.net/user/kutua/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Mar 2, 2025 09:59 AM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_kutua_one_recent_one_total_gallery.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
context "the submission is not yet known" do
|
|
it "updates the num_submissions count" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.num_submissions }.from(nil).to(1)
|
|
end
|
|
|
|
it "creates the submission" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.posts.count }.from(0).to(1)
|
|
post = user.posts.first
|
|
# only know the creator and fa_id when seen from the user page
|
|
expect(post.fa_id).to eq(60_073_062)
|
|
expect(post.title).to be_nil
|
|
expect(post.description).to be_nil
|
|
expect(post.file).to be_nil
|
|
end
|
|
|
|
it "marks the gallery as scanned" do
|
|
perform_now({ user: })
|
|
expect(user.scanned_gallery_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
|
|
it "enqueues a ScanPostJob" do
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match(
|
|
[
|
|
hash_including(
|
|
post: user.posts.first,
|
|
caused_by_entry: @log_entries[0],
|
|
),
|
|
],
|
|
)
|
|
end
|
|
end
|
|
|
|
context "and recent gallery submissions are known" do
|
|
let!(:post) do
|
|
create(
|
|
:domain_post_fa_post,
|
|
fa_id: 60_073_062,
|
|
state: "ok",
|
|
creator: user,
|
|
)
|
|
end
|
|
|
|
shared_examples "force enqueues a ScanPostJob when post is in state" do |state|
|
|
it "force enqueues a ScanPostJob when a post is in the '#{state}' state" do
|
|
post.state = state
|
|
post.save!
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match([hash_including(post:, force_scan: true)])
|
|
end
|
|
end
|
|
|
|
shared_examples "force enqueues a ScanFileJob when post file is in state" do |state|
|
|
it "force enqueues a ScanPostJob when a post's file in the '#{state}' state" do
|
|
file = create(:domain_post_file, post:)
|
|
file.state = state
|
|
file.save!
|
|
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
|
).to match([hash_including(post:, force_scan: true)])
|
|
end
|
|
end
|
|
|
|
%w[removed scan_error file_error].each do |state|
|
|
include_examples(
|
|
"force enqueues a ScanPostJob when post is in state",
|
|
state,
|
|
)
|
|
end
|
|
|
|
%w[file_error retryable_error terminal_error removed].each do |state|
|
|
include_examples(
|
|
"force enqueues a ScanFileJob when post file is in state",
|
|
state,
|
|
)
|
|
end
|
|
|
|
it "force enqueues a ScanFileJob a post's file is in the 'pending' state and has a url" do
|
|
post.state_ok!
|
|
post.save!
|
|
|
|
file =
|
|
create(
|
|
:domain_post_file,
|
|
post:,
|
|
url_str: "https://example.com/file.png",
|
|
)
|
|
file.state_pending!
|
|
file.save!
|
|
|
|
perform_now({ user: })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanFileJob),
|
|
).to match([hash_including(post_file: file)])
|
|
end
|
|
|
|
it "marks the gallery as scanned" do
|
|
perform_now({ user: })
|
|
expect(user.scanned_gallery_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "the user has no recent favories" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/angu/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 27, 2025 04:30 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_angu_no_recent_favorites.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { Domain::User::FaUser.find_by(url_name: "angu") }
|
|
|
|
it "does not enqueue a favs job" do
|
|
perform_now({ url_name: "angu" })
|
|
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob)).to be_empty
|
|
end
|
|
|
|
it "marks scanned_favs_at as recent" do
|
|
perform_now({ url_name: "angu" })
|
|
expect(user.scanned_favs_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
|
|
it "records a favs scan" do
|
|
perform_now({ url_name: "angu" })
|
|
expect(user.favs_scans.count).to eq(1)
|
|
favs_scan = user.favs_scans.first
|
|
expect(favs_scan.log_entry).to eq(@log_entries[0])
|
|
expect(favs_scan.num_added).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "all favorites fit in the recently faved section" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/lleaued/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 27, 2025 04:35 PM"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_lleaued_few_recent_favorites.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:user) { Domain::User::FaUser.find_by(url_name: "lleaued") }
|
|
|
|
it "does not enqueue a favs job" do
|
|
perform_now({ url_name: "lleaued" })
|
|
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob)).to be_empty
|
|
end
|
|
|
|
it "marks scanned_favs_at as recent" do
|
|
perform_now({ url_name: "lleaued" })
|
|
expect(user.scanned_favs_at).to be_within(3.seconds).of(Time.now)
|
|
end
|
|
|
|
it "adds posts to the user's favorites" do
|
|
perform_now({ url_name: "lleaued" })
|
|
expect(user.faved_posts.count).to eq(1)
|
|
expect(user.faved_posts.map(&:fa_id)).to eq([51_355_154])
|
|
end
|
|
|
|
it "works when the user already has some favorites" do
|
|
user = create(:domain_user_fa_user, url_name: "lleaued")
|
|
post = create(:domain_post_fa_post, fa_id: 51_355_154)
|
|
user.update_fav_model(post_id: post.id)
|
|
perform_now({ url_name: "lleaued" })
|
|
expect(user.faved_posts.count).to eq(1)
|
|
expect(user.faved_posts.map(&:fa_id)).to eq([51_355_154])
|
|
end
|
|
|
|
it "records a favs scan" do
|
|
perform_now({ url_name: "lleaued" })
|
|
expect(user.favs_scans.count).to eq(1)
|
|
favs_scan = user.favs_scans.first
|
|
expect(favs_scan.log_entry).to eq(@log_entries[0])
|
|
expect(favs_scan.num_added).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "more favorites than fits in the recent faved section" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/dilgear/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Jun 19, 2025 11:14 AM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_dilgear_many_recent_favorites.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
let(:faved_post_fa_ids) do
|
|
[
|
|
49_881,
|
|
72_900,
|
|
60_088,
|
|
60_086,
|
|
58_000,
|
|
57_298,
|
|
57_282,
|
|
56_811,
|
|
55_187,
|
|
54_431,
|
|
58_488,
|
|
31_830,
|
|
31_425,
|
|
31_413,
|
|
31_409,
|
|
29_859,
|
|
28_961,
|
|
26_304,
|
|
24_977,
|
|
24_451,
|
|
]
|
|
end
|
|
|
|
it "records favs with fav_id and explicit_time" do
|
|
perform_now({ url_name: "dilgear" })
|
|
user = Domain::User::FaUser.find_by(url_name: "dilgear")
|
|
user_post_favs = user.user_post_favs
|
|
expect(user_post_favs.count).to eq(20)
|
|
expect(user_post_favs.map(&:post).map(&:fa_id)).to contain_exactly(
|
|
*faved_post_fa_ids,
|
|
)
|
|
fav_26304 = user_post_favs.find { |f| f.post.fa_id == 26_304 }
|
|
expect(fav_26304.explicit_time).to eq(
|
|
Time.parse("Feb 17, 2006 07:09 AM -08:00"),
|
|
)
|
|
expect(fav_26304.fav_id).to be_nil
|
|
end
|
|
|
|
context "user has not had a favs scan in the past" do
|
|
it "enqueues a favs job" do
|
|
perform_now({ url_name: "dilgear" })
|
|
user = Domain::User::FaUser.find_by(url_name: "dilgear")
|
|
expect(user.scanned_favs_at).to be_nil
|
|
expect(user.user_post_favs.count).to eq(20)
|
|
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob)).to match(
|
|
[hash_including(user:, caused_by_entry: @log_entries[0])],
|
|
)
|
|
end
|
|
|
|
it "does not record a favs scan" do
|
|
perform_now({ url_name: "dilgear" })
|
|
user = Domain::User::FaUser.find_by(url_name: "dilgear")
|
|
expect(user.favs_scans.count).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "the user has had a favs scan in the past" do
|
|
let(:scanned_favs_at) { 1.year.ago }
|
|
let(:user) do
|
|
create(:domain_user_fa_user, url_name: "dilgear", scanned_favs_at:)
|
|
end
|
|
let(:faved_post_fa_ids) do
|
|
[
|
|
# newer favs
|
|
49_881,
|
|
72_900,
|
|
60_088,
|
|
60_086,
|
|
58_000,
|
|
57_298,
|
|
57_282,
|
|
56_811,
|
|
55_187,
|
|
54_431,
|
|
58_488,
|
|
31_830,
|
|
31_425,
|
|
31_413,
|
|
31_409,
|
|
29_859,
|
|
28_961,
|
|
26_304,
|
|
24_977,
|
|
24_451,
|
|
# older favs
|
|
]
|
|
end
|
|
|
|
shared_examples "does not enqueue a favs job" do
|
|
it "does not enqueue a favs job" do
|
|
perform_now({ user: })
|
|
user.reload
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob),
|
|
).to be_empty
|
|
end
|
|
end
|
|
|
|
shared_examples "enqueues a favs job" do
|
|
it "enqueues a favs job" do
|
|
perform_now({ user: })
|
|
expect(SpecUtil.enqueued_job_args(Domain::Fa::Job::FavsJob)).to match(
|
|
[hash_including(user:, caused_by_entry: @log_entries[0])],
|
|
)
|
|
end
|
|
|
|
it "does not record a favs scan" do
|
|
perform_now({ user: })
|
|
expect(user.favs_scans.count).to eq(0)
|
|
end
|
|
end
|
|
|
|
shared_examples "marks scanned_favs_at as recent" do
|
|
it "marks scanned_favs_at as recent" do
|
|
expect do
|
|
perform_now({ user: })
|
|
user.reload
|
|
end.to change { user.scanned_favs_at }.to be_within(3.seconds).of(
|
|
Time.now,
|
|
)
|
|
end
|
|
end
|
|
|
|
shared_examples "does not mark scanned_favs_at as recent" do
|
|
it "does not mark scanned_favs_at as recent" do
|
|
expect {
|
|
perform_now({ user: })
|
|
user.reload
|
|
}.to_not change { user.scanned_favs_at }
|
|
end
|
|
end
|
|
|
|
shared_examples "does not change the user's favorites" do
|
|
it "does not change the user's favorites" do
|
|
expect {
|
|
perform_now({ user: })
|
|
user.reload
|
|
}.to_not change { user.faved_posts.map(&:fa_id).sort }
|
|
end
|
|
end
|
|
|
|
context "and the user has no known favorites" do
|
|
include_examples "enqueues a favs job"
|
|
include_examples "does not mark scanned_favs_at as recent"
|
|
# include_examples "does not change the user's favorites"
|
|
end
|
|
|
|
context "all user's recent favorites are known" do
|
|
before do
|
|
faved_post_fa_ids.each do |fa_post_id|
|
|
post = create(:domain_post_fa_post, fa_id: fa_post_id)
|
|
user.update_fav_model(post_id: post.id)
|
|
end
|
|
end
|
|
|
|
include_examples "does not enqueue a favs job"
|
|
include_examples "marks scanned_favs_at as recent"
|
|
# include_examples "does not change the user's favorites"
|
|
end
|
|
|
|
context "all but the last favorite are known" do
|
|
before do
|
|
faved_post_fa_ids[0..-2].each do |fa_post_id|
|
|
post = create(:domain_post_fa_post, fa_id: fa_post_id)
|
|
user.update_fav_model(post_id: post.id)
|
|
end
|
|
end
|
|
|
|
include_examples "enqueues a favs job"
|
|
include_examples "does not mark scanned_favs_at as recent"
|
|
# include_examples "does not change the user's favorites"
|
|
end
|
|
|
|
context "favorites in the middle are unknown" do
|
|
before do
|
|
(faved_post_fa_ids[..5] + faved_post_fa_ids[8..]).each do |fa_post_id|
|
|
post = create(:domain_post_fa_post, fa_id: fa_post_id)
|
|
user.update_fav_model(post_id: post.id)
|
|
end
|
|
end
|
|
|
|
include_examples "enqueues a favs job"
|
|
include_examples "does not mark scanned_favs_at as recent"
|
|
# include_examples "does not change the user's favorites"
|
|
end
|
|
|
|
context "favorites at the start are unknown" do
|
|
before do
|
|
faved_post_fa_ids[5..].each do |fa_post_id|
|
|
post = create(:domain_post_fa_post, fa_id: fa_post_id)
|
|
user.update_fav_model(post_id: post.id)
|
|
end
|
|
end
|
|
|
|
include_examples "does not enqueue a favs job"
|
|
include_examples "marks scanned_favs_at as recent"
|
|
|
|
it "adds the new favorites to the user's favorites" do
|
|
expect { perform_now({ user: }) }.to change {
|
|
user.reload
|
|
user.faved_posts.count
|
|
}.by(5)
|
|
|
|
expect(user.faved_posts.count).to eq(faved_post_fa_ids.count)
|
|
expect(user.faved_posts.map(&:fa_id)).to contain_exactly(
|
|
*faved_post_fa_ids,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
context "with a user with buggy favcount" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/marsdust/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("May 2, 2023 12:41 PM"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_marsdust.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "records the right fav count" do
|
|
perform_now({ url_name: "marsdust" })
|
|
user = Domain::User::FaUser.find_by(url_name: "marsdust")
|
|
expect(user).to_not be_nil
|
|
expect(user.avatar.url_str).to eq(
|
|
"https://a.furaffinity.net/1424255659/marsdust.gif",
|
|
)
|
|
expect(user.num_favorites).to eq(0)
|
|
end
|
|
end
|
|
|
|
context "user with page that links to unseen users" do
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/angelpawqt/",
|
|
status_code: 200,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Feb 24, 2025 08:52 PM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_angelpawqt.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "enqueues jobs for the unseen users" do
|
|
perform_now({ url_name: "angelpawqt", skip_enqueue_found_links: false })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserPageJob),
|
|
).to include(
|
|
hash_including(
|
|
user: Domain::User::FaUser.find_by(url_name: "8bitstarshon1"),
|
|
),
|
|
)
|
|
end
|
|
end
|
|
|
|
shared_examples "user not found" do |status_code|
|
|
let(:client_mock_config) do
|
|
[
|
|
{
|
|
uri: "https://www.furaffinity.net/user/onefatpokemon/",
|
|
status_code:,
|
|
content_type: "text/html",
|
|
requested_at: Time.parse("Mar 2, 2025 09:59 AM UTC"),
|
|
contents:
|
|
SpecUtil.read_fixture_file(
|
|
"domain/fa/user_page/user_page_onefatpokemon_not_found.html",
|
|
),
|
|
},
|
|
]
|
|
end
|
|
|
|
it "does not enqueue a user page job" do
|
|
perform_now({ url_name: "onefatpokemon" })
|
|
expect(
|
|
SpecUtil.enqueued_job_args(Domain::Fa::Job::UserPageJob),
|
|
).to be_empty
|
|
end
|
|
|
|
it "marks the user as error" do
|
|
perform_now({ url_name: "onefatpokemon" })
|
|
user = Domain::User::FaUser.find_by(url_name: "onefatpokemon")
|
|
expect(user).to_not be_nil
|
|
expect(user.state).to eq("error")
|
|
end
|
|
end
|
|
|
|
context "user not found with 200 status code" do
|
|
include_examples "user not found", 200
|
|
end
|
|
|
|
context "user not found with 400 status code" do
|
|
include_examples "user not found", 400
|
|
end
|
|
end
|