355 lines
11 KiB
Ruby
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
|