Files
redux-scraper/spec/jobs/domain/bluesky/job/scan_user_job_spec.rb
2025-08-15 22:15:09 +00:00

355 lines
11 KiB
Ruby

# typed: false
# frozen_string_literal: true
require "rails_helper"
RSpec.describe Domain::Bluesky::Job::ScanUserJob do
include PerformJobHelpers
let(:user) do
create(
:domain_user_bluesky_user,
did: "did:plc:test123",
handle: "testuser.bsky.social",
scanned_profile_at: nil,
scanned_posts_at: nil,
)
end
describe "#perform" do
context "when user profile scanning is due" do
let(:profile_response_body) do
{
"did" => user.did,
"handle" => "testuser.bsky.social",
"displayName" => "Test User",
"description" => "A test user profile",
"avatar" =>
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
"createdAt" => "2023-07-03T05:08:27.780Z",
}.to_json
end
let(:client_mock_config) do
[
{
uri:
"https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=#{user.did}",
status_code: 200,
content_type: "application/json",
contents: profile_response_body,
},
]
end
before do
@log_entries = HttpClientMockHelpers.init_with(client_mock_config)
end
it "scans user profile and updates user data" do
perform_now({ user: user })
user.reload
expect(user.display_name).to eq("Test User")
expect(user.description).to eq("A test user profile")
expect(user.scanned_profile_at).to be_present
expect(user.state).to eq("ok")
expect(user.last_scan_log_entry).to eq(@log_entries.first)
expect(user.registered_at).to eq(
Time.parse("2023-07-03T05:08:27.780Z").in_time_zone("UTC"),
)
end
it "sets last_scan_log_entry to the HTTP response log entry" do
perform_now({ user: user })
user.reload
expect(user.last_scan_log_entry).to be_present
expect(user.last_scan_log_entry).to eq(@log_entries.first)
end
it "does not enqueue a ScanPostsJob" do
# Clear any existing enqueued jobs first
SpecUtil.clear_enqueued_jobs!
perform_now({ user: user })
# Check that ScanPostsJob was not enqueued
enqueued_jobs =
SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanPostsJob)
expect(enqueued_jobs).to be_empty
end
it "creates avatar for user with pending state" do
expect { perform_now({ user: user }) }.to change {
user.reload.avatar.present?
}.from(false).to(true)
avatar = user.reload.avatar
expect(avatar.url_str).to eq(
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
)
expect(avatar.state).to eq("pending")
end
it "enqueues Domain::UserAvatarJob when creating new avatar" do
# Clear any existing enqueued jobs first
SpecUtil.clear_enqueued_jobs!
perform_now({ user: user })
avatar = user.reload.avatar
expect(avatar).to be_present
# Check that UserAvatarJob was enqueued
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to contain_exactly(hash_including(avatar: avatar))
end
end
context "avatar handling scenarios" do
let(:profile_response_body) do
{
"did" => user.did,
"handle" => "testuser.bsky.social",
"displayName" => "Test User",
"description" => "A test user profile",
"avatar" =>
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
"createdAt" => "2023-07-03T05:08:27.780Z",
}.to_json
end
let(:client_mock_config) do
[
{
uri:
"https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=#{user.did}",
status_code: 200,
content_type: "application/json",
contents: profile_response_body,
},
]
end
before do
@log_entries = HttpClientMockHelpers.init_with(client_mock_config)
end
context "when user has existing avatar with same URL" do
let!(:existing_avatar) do
user.create_avatar!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
state: "ok",
)
end
it "does not enqueue job for already downloaded avatar with same URL" do
perform_now({ user: user })
# Avatar should remain unchanged
existing_avatar.reload
expect(existing_avatar.state).to eq("ok")
# Should not enqueue any avatar job
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to be_empty
end
end
context "when user has existing avatar with different URL" do
let!(:existing_avatar) do
user.create_avatar!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=oldavatar456",
state: "ok",
)
end
it "creates a new avatar and enqueues job" do
# Verify initial state
expect(user.avatars.count).to eq(1)
expect(user.avatar).to eq(existing_avatar)
perform_now({ user: user })
user.reload
existing_avatar.reload
# Should have 2 avatars now - old one + new one
expect(user.avatars.count).to eq(2)
# The current avatar should be the new one (most recent)
new_avatar = user.avatar
expect(new_avatar).to_not eq(existing_avatar)
expect(new_avatar.url_str).to eq(
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
)
expect(new_avatar.state).to eq("pending")
# Old avatar should remain unchanged
expect(existing_avatar.url_str).to eq(
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=oldavatar456",
)
expect(existing_avatar.state).to eq("ok")
# Should enqueue avatar job for the new avatar
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to contain_exactly(
hash_including(avatar: new_avatar),
)
end
end
context "when user has existing avatar and profile has no avatar" do
let!(:existing_avatar) do
user.create_avatar!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=oldavatar456",
state: "ok",
)
end
let(:profile_response_body) do
{
"did" => user.did,
"handle" => "testuser.bsky.social",
"displayName" => "Test User",
"description" => "A test user profile",
"createdAt" => "2023-07-03T05:08:27.780Z",
}.to_json
end
it "does not create new avatar or enqueue job" do
expect(user.avatars.count).to eq(1)
perform_now({ user: user })
user.reload
expect(user.avatars.count).to eq(1)
expect(user.avatar).to eq(existing_avatar)
# Should not enqueue any avatar job
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to be_empty
end
end
context "when user has multiple existing avatars" do
let!(:first_avatar) do
user.avatars.create!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=firstavatar123",
state: "ok",
created_at: 2.days.ago,
)
end
let!(:second_avatar) do
user.avatars.create!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=secondavatar456",
state: "ok",
created_at: 1.day.ago,
)
end
it "compares against the current (most recent) avatar" do
expect(user.avatar).to eq(second_avatar)
expect(user.avatars.count).to eq(2)
perform_now({ user: user })
user.reload
# Should create a third avatar since URL is different from current
expect(user.avatars.count).to eq(3)
new_avatar = user.avatar
expect(new_avatar.url_str).to eq(
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
)
# Should enqueue job for new avatar
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to contain_exactly(
hash_including(avatar: new_avatar),
)
end
end
context "when user has existing avatar in pending state" do
let!(:existing_avatar) do
user.create_avatar!(
url_str:
"https://bsky.social/xrpc/com.atproto.sync.getBlob?did=#{user.did}&cid=bafkreiavatar123",
state: "pending",
)
end
it "re-enqueues job for pending avatar with same URL" do
perform_now({ user: user })
existing_avatar.reload
expect(existing_avatar.state).to eq("pending")
# Should re-enqueue avatar job
enqueued_jobs = SpecUtil.enqueued_job_args(Domain::UserAvatarJob)
expect(enqueued_jobs).to contain_exactly(
hash_including(avatar: existing_avatar),
)
end
end
end
context "when user already scanned recently" do
before do
user.update!(scanned_profile_at: 1.day.ago, scanned_posts_at: 1.day.ago)
end
it "skips scanning if not due" do
expect(Scraper::ClientFactory.http_client_mock).not_to receive(:get)
perform_now({ user: user })
end
end
end
describe "user creation callback" do
it "enqueues scan job when user is created" do
# Clear any existing enqueued jobs first
SpecUtil.clear_enqueued_jobs!
created_user =
create(
:domain_user_bluesky_user,
did: "did:plc:newuser123",
handle: "newuser.bsky.social",
)
# Check that ScanUserJob was enqueued with correct arguments
enqueued_jobs =
SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanUserJob)
expect(enqueued_jobs).to contain_exactly(
hash_including(user: created_user),
)
end
it "does not enqueue scan job for users in error state" do
# Clear any existing enqueued jobs first
SpecUtil.clear_enqueued_jobs!
create(
:domain_user_bluesky_user,
did: "did:plc:erroruser123",
handle: "erroruser.bsky.social",
state: "error",
)
# Check that no ScanUserJob was enqueued
enqueued_jobs =
SpecUtil.enqueued_job_args(Domain::Bluesky::Job::ScanUserJob)
expect(enqueued_jobs).to be_empty
end
end
end