migrate inkbunny jobs to unified domain models

This commit is contained in:
Dylan Knutson
2025-02-12 19:26:02 +00:00
parent c2cbe78fd1
commit 6c253818ff
40 changed files with 509 additions and 223 deletions

View File

@@ -6,28 +6,30 @@
# LatestPostsJob, which needs to process a single page of submissions.
class Domain::Inkbunny::Job::ApiSearchPageProcessor
extend T::Sig
include HasColorLogger
SUBMISSIONS_PER_PAGE = 100
MAX_LOOP_COUNT = 50
sig { returns(T::Array[Domain::Inkbunny::Post]) }
sig { returns(T::Array[Domain::Post::InkbunnyPost]) }
attr_reader :changed_posts
sig { void }
def initialize
@shallow_posts_by_ib_post_id =
T.let({}, T::Hash[Integer, Domain::Inkbunny::Post])
@users_by_ib_user_id = T.let({}, T::Hash[Integer, Domain::Inkbunny::User])
@changed_posts = T.let([], T::Array[Domain::Inkbunny::Post])
T.let({}, T::Hash[Integer, Domain::Post::InkbunnyPost])
@users_by_ib_user_id =
T.let({}, T::Hash[Integer, Domain::User::InkbunnyUser])
@changed_posts = T.let([], T::Array[Domain::Post::InkbunnyPost])
@total_new_posts = T.let(0, Integer)
end
sig { returns(T::Array[Domain::Inkbunny::Post]) }
sig { returns(T::Array[Domain::Post::InkbunnyPost]) }
def all_posts
@shallow_posts_by_ib_post_id.values
end
sig { returns(T::Array[Domain::Inkbunny::User]) }
sig { returns(T::Array[Domain::User::InkbunnyUser]) }
def all_users
@users_by_ib_user_id.values
end
@@ -149,8 +151,8 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
return false if post = @shallow_posts_by_ib_post_id[ib_post_id]
post =
Domain::Inkbunny::Post.includes(:creator).find_or_initialize_by(
ib_post_id: ib_post_id,
Domain::Post::InkbunnyPost.includes(:creator).find_or_initialize_by(
ib_id: ib_post_id,
)
@shallow_posts_by_ib_post_id[ib_post_id] = post
is_new_post = post.new_record?
@@ -161,12 +163,12 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
caused_by_entry: caused_by_entry,
)
if post.creator && (post.creator&.ib_user_id != creator.ib_user_id)
raise "post.creator.ib_user_id (#{post.creator&.ib_user_id}) != creator.ib_user_id (#{creator.ib_user_id})"
if post.creator && (post.creator&.ib_id != creator.ib_id)
raise "post.creator.ib_id (#{post.creator&.ib_id}) != creator.ib_id (#{creator.ib_id})"
end
post.creator = creator
post.shallow_updated_at = Time.zone.now
post.shallow_updated_at = Time.current
post.title = submission_json["title"]
post.posted_at =
Time.parse(submission_json["create_datetime"]).in_time_zone("UTC")
@@ -180,13 +182,18 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
end
post.num_files = submission_json["pagecount"]&.to_i
post.rating = submission_json["rating_id"]&.to_i
post.submission_type = submission_json["submission_type_id"]&.to_i
if rating_id = submission_json["rating_id"]&.to_i
post.rating = Domain::Post::InkbunnyPost::RATING_TYPE_MAP[rating_id]
end
if submission_type_id = submission_json["submission_type_id"]&.to_i
post.submission_type =
Domain::Post::InkbunnyPost::SUBMISSION_TYPE_MAP[submission_type_id]
end
post.ib_detail_raw["submission_json"] = submission_json
post_changed =
post.changed? || post.files.count != post.num_files ||
creator.avatar_url_str.blank?
creator.avatar.blank?
if post_changed
post.shallow_update_log_entry = caused_by_entry
@@ -207,7 +214,7 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
params(
submission_json: T::Hash[String, T.untyped],
caused_by_entry: T.nilable(HttpLogEntry),
).returns(Domain::Inkbunny::User)
).returns(Domain::User::InkbunnyUser)
end
def upsert_user_from_submission_json!(submission_json, caused_by_entry: nil)
ib_user_id = submission_json["user_id"]&.to_i
@@ -215,10 +222,13 @@ class Domain::Inkbunny::Job::ApiSearchPageProcessor
return user
end
raise "ib_user_id is blank" if ib_user_id.blank?
user = Domain::Inkbunny::User.find_or_initialize_by(ib_user_id: ib_user_id)
user = Domain::User::InkbunnyUser.find_or_initialize_by(ib_id: ib_user_id)
@users_by_ib_user_id[ib_user_id] = user
user.name = submission_json["username"]
if user.changed?
logger.info(
"[ApiSearchPageProcessor][user changed][ib_id: #{user.ib_id_change&.join(" -> ")}][name: #{user.name_change&.join(" -> ")}]",
)
user.shallow_update_log_entry = caused_by_entry
user.save!
end

View File

@@ -10,12 +10,12 @@ class Domain::Inkbunny::Job::Base < Scraper::JobBase
:get_inkbunny_http_client
end
sig { returns(T.nilable(Domain::Inkbunny::User)) }
sig { returns(T.nilable(Domain::User::InkbunnyUser)) }
def user_from_args
T.cast(arguments[0][:user], T.nilable(Domain::Inkbunny::User))
T.cast(arguments[0][:user], T.nilable(Domain::User::InkbunnyUser))
end
sig { returns(Domain::Inkbunny::User) }
sig { returns(Domain::User::InkbunnyUser) }
def user_from_args!
user_from_args || raise("user must exist")
end

View File

@@ -62,7 +62,7 @@ module Domain::Inkbunny::Job
defer_job(
Domain::Inkbunny::Job::UpdatePostsJob,
{
ib_post_ids: posts_to_update.map(&:ib_post_id),
ib_post_ids: posts_to_update.map(&:ib_id),
caused_by_entry: first_log_entry,
},
)

View File

@@ -63,7 +63,7 @@ module Domain::Inkbunny::Job
if posts_to_update.any?
defer_job(
Domain::Inkbunny::Job::UpdatePostsJob,
{ ib_post_ids: posts_to_update.map(&:ib_post_id) },
{ ib_post_ids: posts_to_update.map(&:ib_id) },
)
end
end

View File

@@ -3,61 +3,45 @@ module Domain::Inkbunny::Job
class UserAvatarJob < Base
queue_as :static_file
sig { params(args: T.untyped).void }
def initialize(*args)
super(*T.unsafe(args))
end
sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
def perform(args)
user = user_from_args!
avatar = T.must(user.avatar)
logger.prefix =
proc { "[user #{user.name.to_s.bold} / #{user.ib_user_id.to_s.bold}]" }
proc { "[user #{user.name.to_s.bold} / #{user.ib_id.to_s.bold}]" }
avatar_url_str = user.avatar_url_str
if avatar_url_str.blank?
logger.warn("user has no avatar_url_str")
url_str = avatar.url_str
if url_str.blank?
logger.warn("avatar has no url_str")
return
end
response = http_client.get(avatar_url_str)
response = http_client.get(url_str)
self.first_log_entry ||= response.log_entry
user.avatar_state_detail ||= {}
user.avatar_state_detail["log_entries"] ||= [
user.avatar_file_log_entry_id,
].compact
user.avatar_state_detail["log_entries"] << response.log_entry.id
user.avatar_log_entry = response.log_entry
avatar.last_log_entry = response.log_entry
case response.status_code
when 200
user.avatar_state = :ok
user.avatar_state_detail.delete("download_error")
user.avatar_downloaded_at = response.log_entry.created_at
user.avatar_file_sha256 = response.log_entry.response_sha256
avatar.state = "ok"
avatar.error_message = nil
avatar.downloaded_at = response.log_entry.created_at
avatar.log_entry = response.log_entry
logger.info("downloaded avatar")
when 404
user.avatar_state = :not_found
avatar.state = "file_404"
avatar.error_message = "http #{response.status_code}"
logger.info("avatar 404")
else
user.avatar_state = :error
user.avatar_state_detail[
"download_error"
] = "http status #{response.status_code}"
if user.avatar_file_sha256.blank?
user.avatar_downloaded_at = response.log_entry.created_at
logger.info("avatar error, and no previous file")
else
logger.info("avatar error, keeping previous file")
end
avatar.state = "http_error"
avatar.error_message = "http #{response.status_code}"
fatal_error(
"http #{response.status_code}, log entry #{response.log_entry.id}",
)
end
ensure
user.save! if user
avatar.save! if avatar
end
end
end

View File

@@ -1,9 +1,10 @@
# typed: true
# typed: strict
module Domain::Inkbunny::Job
class UserGalleryJob < Base
sig { override.params(args: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
def perform(args)
user = user_from_args!
logger.prefix = "[#{user.name&.bold} / #{user.ib_user_id.to_s.bold}]"
logger.prefix = "[#{user.name&.bold} / #{user.ib_id.to_s.bold}]"
if user.scanned_gallery_at&.after?(1.week.ago)
logger.warn(
@@ -24,7 +25,7 @@ module Domain::Inkbunny::Job
url =
ApiSearchPageProcessor.build_api_search_url(
ib_user_id: user.ib_user_id,
ib_user_id: user.ib_id,
rid: rid,
page: page,
)
@@ -50,6 +51,7 @@ module Domain::Inkbunny::Job
].join(" "),
)
user.reload
if user.scanned_gallery_at.present? && num_new_posts == 0
logger.info("[no new posts, stopping]")
break
@@ -65,7 +67,7 @@ module Domain::Inkbunny::Job
if processor.changed_posts.any?
defer_job(
Domain::Inkbunny::Job::UpdatePostsJob,
{ ib_post_ids: processor.changed_posts.map(&:ib_post_id) },
{ ib_post_ids: processor.changed_posts.map(&:ib_id) },
)
end
end

View File

@@ -1,5 +1,34 @@
# typed: strict
# frozen_string_literal: true
class Domain::Post::InkbunnyPost < Domain::Post
SUBMISSION_TYPE_MAP =
T.let(
{
0 => "unknown",
1 => "picture_pinup",
2 => "sketch",
3 => "picture_series",
4 => "comic",
5 => "portfolio",
6 => "flash_animation",
7 => "flash_interactive",
8 => "video_feature",
9 => "video_animation",
10 => "music_single",
11 => "music_album",
12 => "writing_document",
13 => "character_sheet",
14 => "photography",
}.freeze,
T::Hash[Integer, String],
)
RATING_TYPE_MAP =
T.let(
{ 0 => "general", 1 => "mature", 2 => "adult" }.freeze,
T::Hash[Integer, String],
)
attr_json :ib_id, :integer
attr_json :state, :string
attr_json :rating, :string
@@ -14,6 +43,9 @@ class Domain::Post::InkbunnyPost < Domain::Post
attr_json :last_file_updated_at, :datetime
attr_json :deep_update_log_entry_id, :integer
attr_json :shallow_update_log_entry_id, :integer
attr_json :shallow_updated_at, :datetime
attr_json :deep_updated_at, :datetime
attr_json :ib_detail_raw, ActiveModel::Type::Value.new
has_multiple_files! Domain::PostFile::InkbunnyPostFile
has_single_creator! Domain::User::InkbunnyUser
@@ -30,28 +62,17 @@ class Domain::Post::InkbunnyPost < Domain::Post
validates :ib_id, presence: true
validates :state, presence: true, inclusion: { in: %w[ok error] }
validates :rating, presence: true, inclusion: { in: %w[general mature adult] }
validates :rating, inclusion: { in: RATING_TYPE_MAP.values }, if: :rating
validates :submission_type,
presence: true,
inclusion: {
in: %w[
unknown
picture_pinup
sketch
picture_series
comic
portfolio
flash_animation
flash_interactive
video_feature
video_animation
music_single
music_album
writing_document
character_sheet
photography
],
}
in: SUBMISSION_TYPE_MAP.values,
},
if: :submission_type
after_initialize do
self.state ||= "ok"
self.ib_detail_raw ||= {}
end
sig { override.returns([String, Symbol]) }
def self.param_prefix_and_attribute

View File

@@ -41,7 +41,10 @@ class Domain::User < ReduxApplicationRecord
super
end
before_save { self.name_for_search = names_for_search_value }
before_save do
self.name_for_search = names_for_search_value
true
end
sig(:final) { returns(String) }
def names_for_search_value

View File

@@ -6,6 +6,7 @@ class Domain::User::InkbunnyUser < Domain::User
attr_json :scanned_gallery_at, :datetime
attr_json :deep_update_log_entry_id, :integer
attr_json :shallow_update_log_entry_id, :integer
attr_json :ib_detail_raw, ActiveModel::Type::Value.new
has_many :posts,
class_name: "::Domain::Inkbunny::Post",
@@ -27,6 +28,11 @@ class Domain::User::InkbunnyUser < Domain::User
validates :name, presence: true
validates :state, presence: true, inclusion: { in: %w[ok error] }
after_initialize do
self.state ||= "ok"
self.ib_detail_raw ||= {}
end
sig { override.returns([String, Symbol]) }
def self.param_prefix_and_attribute
["ib", :name]
@@ -57,4 +63,13 @@ class Domain::User::InkbunnyUser < Domain::User
def names_for_search
[name].compact
end
sig { returns(T::Boolean) }
def due_for_gallery_scan?
if at = self.scanned_gallery_at
at < 1.month.ago
else
true
end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::E621::Job::PostsIndexJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::E621::Job::ScanPostFavsJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::E621::Job::ScanPostFavsJob).void)
).returns(T.any(Domain::E621::Job::ScanPostFavsJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::E621::Job::ScanPostJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::E621::Job::ScanUserFavsJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::E621::Job::ScanUserFavsJob).void)
).returns(T.any(Domain::E621::Job::ScanUserFavsJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::E621::Job::ScanUsersJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::E621::Job::ScanUsersJob).void)
).returns(T.any(Domain::E621::Job::ScanUsersJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::E621::Job::StaticFileJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::BrowsePageJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::FavsJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::ScanFileJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::ScanPostJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::Fa::Job::UserAvatarJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::Fa::Job::UserAvatarJob).void)
).returns(T.any(Domain::Fa::Job::UserAvatarJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::UserFollowsJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::UserGalleryJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::UserIncrementalJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Fa::Job::UserPageJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -0,0 +1,16 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for dynamic methods in `Domain::Inkbunny::Job::ApiSearchPageProcessor`.
# Please instead update this file by running `bin/tapioca dsl Domain::Inkbunny::Job::ApiSearchPageProcessor`.
class Domain::Inkbunny::Job::ApiSearchPageProcessor
sig { returns(ColorLogger) }
def logger; end
class << self
sig { returns(ColorLogger) }
def logger; end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::Inkbunny::Job::FileJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::Inkbunny::Job::FileJob).void)
).returns(T.any(Domain::Inkbunny::Job::FileJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Inkbunny::Job::LatestPostsJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Inkbunny::Job::UpdatePoolJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -21,7 +21,7 @@ class Domain::Inkbunny::Job::UpdatePostsJob
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -15,13 +15,13 @@ class Domain::Inkbunny::Job::UserAvatarJob
sig do
params(
args: T.untyped,
args: T::Hash[::Symbol, T.untyped],
block: T.nilable(T.proc.params(job: Domain::Inkbunny::Job::UserAvatarJob).void)
).returns(T.any(Domain::Inkbunny::Job::UserAvatarJob, FalseClass))
end
def perform_later(args, &block); end
sig { params(args: T.untyped).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -895,6 +895,106 @@ class Domain::Post::InkbunnyPost
sig { void }
def deep_update_log_entry_id_will_change!; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at; end
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at=(value); end
sig { returns(T::Boolean) }
def deep_updated_at?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at_before_last_save; end
sig { returns(T.untyped) }
def deep_updated_at_before_type_cast; end
sig { returns(T::Boolean) }
def deep_updated_at_came_from_user?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def deep_updated_at_change; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def deep_updated_at_change_to_be_saved; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def deep_updated_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at_in_database; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def deep_updated_at_previous_change; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def deep_updated_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at_previously_was; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def deep_updated_at_was; end
sig { void }
def deep_updated_at_will_change!; end
sig { returns(T.untyped) }
def ib_detail_raw; end
sig { params(value: T.untyped).returns(T.untyped) }
def ib_detail_raw=(value); end
sig { returns(T::Boolean) }
def ib_detail_raw?; end
sig { returns(T.untyped) }
def ib_detail_raw_before_last_save; end
sig { returns(T.untyped) }
def ib_detail_raw_before_type_cast; end
sig { returns(T::Boolean) }
def ib_detail_raw_came_from_user?; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_change; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_change_to_be_saved; end
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
def ib_detail_raw_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.untyped) }
def ib_detail_raw_in_database; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_previous_change; end
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
def ib_detail_raw_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.untyped) }
def ib_detail_raw_previously_was; end
sig { returns(T.untyped) }
def ib_detail_raw_was; end
sig { void }
def ib_detail_raw_will_change!; end
sig { returns(T.nilable(::Integer)) }
def ib_id; end
@@ -1506,6 +1606,12 @@ class Domain::Post::InkbunnyPost
sig { void }
def restore_deep_update_log_entry_id!; end
sig { void }
def restore_deep_updated_at!; end
sig { void }
def restore_ib_detail_raw!; end
sig { void }
def restore_ib_id!; end
@@ -1548,6 +1654,9 @@ class Domain::Post::InkbunnyPost
sig { void }
def restore_shallow_update_log_entry_id!; end
sig { void }
def restore_shallow_updated_at!; end
sig { void }
def restore_state!; end
@@ -1578,6 +1687,18 @@ class Domain::Post::InkbunnyPost
sig { returns(T::Boolean) }
def saved_change_to_deep_update_log_entry_id?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def saved_change_to_deep_updated_at; end
sig { returns(T::Boolean) }
def saved_change_to_deep_updated_at?; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def saved_change_to_ib_detail_raw; end
sig { returns(T::Boolean) }
def saved_change_to_ib_detail_raw?; end
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
def saved_change_to_ib_id; end
@@ -1662,6 +1783,12 @@ class Domain::Post::InkbunnyPost
sig { returns(T::Boolean) }
def saved_change_to_shallow_update_log_entry_id?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def saved_change_to_shallow_updated_at; end
sig { returns(T::Boolean) }
def saved_change_to_shallow_updated_at?; end
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
def saved_change_to_state; end
@@ -1743,6 +1870,61 @@ class Domain::Post::InkbunnyPost
sig { void }
def shallow_update_log_entry_id_will_change!; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at; end
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at=(value); end
sig { returns(T::Boolean) }
def shallow_updated_at?; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at_before_last_save; end
sig { returns(T.untyped) }
def shallow_updated_at_before_type_cast; end
sig { returns(T::Boolean) }
def shallow_updated_at_came_from_user?; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def shallow_updated_at_change; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def shallow_updated_at_change_to_be_saved; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def shallow_updated_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at_in_database; end
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
def shallow_updated_at_previous_change; end
sig do
params(
from: T.nilable(::ActiveSupport::TimeWithZone),
to: T.nilable(::ActiveSupport::TimeWithZone)
).returns(T::Boolean)
end
def shallow_updated_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at_previously_was; end
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def shallow_updated_at_was; end
sig { void }
def shallow_updated_at_will_change!; end
sig { returns(T.nilable(::String)) }
def state; end
@@ -1984,6 +2166,12 @@ class Domain::Post::InkbunnyPost
sig { returns(T::Boolean) }
def will_save_change_to_deep_update_log_entry_id?; end
sig { returns(T::Boolean) }
def will_save_change_to_deep_updated_at?; end
sig { returns(T::Boolean) }
def will_save_change_to_ib_detail_raw?; end
sig { returns(T::Boolean) }
def will_save_change_to_ib_id?; end
@@ -2026,6 +2214,9 @@ class Domain::Post::InkbunnyPost
sig { returns(T::Boolean) }
def will_save_change_to_shallow_update_log_entry_id?; end
sig { returns(T::Boolean) }
def will_save_change_to_shallow_updated_at?; end
sig { returns(T::Boolean) }
def will_save_change_to_state?; end

View File

@@ -895,6 +895,51 @@ class Domain::User::InkbunnyUser
sig { void }
def deep_update_log_entry_id_will_change!; end
sig { returns(T.untyped) }
def ib_detail_raw; end
sig { params(value: T.untyped).returns(T.untyped) }
def ib_detail_raw=(value); end
sig { returns(T::Boolean) }
def ib_detail_raw?; end
sig { returns(T.untyped) }
def ib_detail_raw_before_last_save; end
sig { returns(T.untyped) }
def ib_detail_raw_before_type_cast; end
sig { returns(T::Boolean) }
def ib_detail_raw_came_from_user?; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_change; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_change_to_be_saved; end
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
def ib_detail_raw_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.untyped) }
def ib_detail_raw_in_database; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def ib_detail_raw_previous_change; end
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
def ib_detail_raw_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
sig { returns(T.untyped) }
def ib_detail_raw_previously_was; end
sig { returns(T.untyped) }
def ib_detail_raw_was; end
sig { void }
def ib_detail_raw_will_change!; end
sig { returns(T.nilable(::Integer)) }
def ib_id; end
@@ -1226,6 +1271,9 @@ class Domain::User::InkbunnyUser
sig { void }
def restore_deep_update_log_entry_id!; end
sig { void }
def restore_ib_detail_raw!; end
sig { void }
def restore_ib_id!; end
@@ -1274,6 +1322,12 @@ class Domain::User::InkbunnyUser
sig { returns(T::Boolean) }
def saved_change_to_deep_update_log_entry_id?; end
sig { returns(T.nilable([T.untyped, T.untyped])) }
def saved_change_to_ib_detail_raw; end
sig { returns(T::Boolean) }
def saved_change_to_ib_detail_raw?; end
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
def saved_change_to_ib_id; end
@@ -1597,6 +1651,9 @@ class Domain::User::InkbunnyUser
sig { returns(T::Boolean) }
def will_save_change_to_deep_update_log_entry_id?; end
sig { returns(T::Boolean) }
def will_save_change_to_ib_detail_raw?; end
sig { returns(T::Boolean) }
def will_save_change_to_ib_id?; end

View File

@@ -21,7 +21,7 @@ class Scraper::JobBase
end
def perform_later(args, &block); end
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
sig { params(args: T::Hash[::Symbol, T.untyped]).returns(T.untyped) }
def perform_now(args); end
end
end

View File

@@ -0,0 +1,8 @@
# typed: false
FactoryBot.define do
factory :domain_post_inkbunny_post, class: "Domain::Post::InkbunnyPost" do
sequence(:ib_id) { |n| 1 + n }
sequence(:title) { |n| "the_post_#{n}" }
association :creator, factory: :domain_user_inkbunny_user
end
end

View File

@@ -0,0 +1,7 @@
# typed: false
FactoryBot.define do
factory :domain_user_inkbunny_user, class: "Domain::User::InkbunnyUser" do
sequence(:ib_id) { |n| 1 + n }
sequence(:name) { |n| "the_user_#{n}" }
end
end

View File

@@ -1,7 +1,7 @@
# typed: false
FactoryBot.define do
factory :domain_user_avatar, class: "Domain::UserAvatar" do
user { create(:domain_user_fa_user) }
association :user, factory: :domain_user_fa_user
state { "ok" }
url_str { "https://a.furaffinity.net/0/meesh.gif" }
log_entry { create(:http_log_entry) }

View File

@@ -26,38 +26,38 @@ describe Domain::Inkbunny::Job::LatestPostsJob do
it "creates posts" do
expect { perform_now({}) }.to(
change(Domain::Inkbunny::Post, :count)
change(Domain::Post::InkbunnyPost, :count)
.by(3)
.and(change(Domain::Inkbunny::User, :count).by(3))
.and(change(Domain::User::InkbunnyUser, :count).by(3))
.and(change(Domain::Inkbunny::File, :count).by(0)),
)
user_thendyart = Domain::Inkbunny::User.find_by!(ib_user_id: 941_565)
user_thendyart = Domain::User::InkbunnyUser.find_by!(ib_id: 941_565)
expect(user_thendyart.name).to eq("ThendyArt")
user_seff = Domain::Inkbunny::User.find_by!(ib_user_id: 229_331)
user_seff = Domain::User::InkbunnyUser.find_by!(ib_id: 229_331)
expect(user_seff.name).to eq("Seff")
expect(user_seff.avatar_url_str).to be_nil
expect(user_seff.avatar).to be_nil
post_3104202 = Domain::Inkbunny::Post.find_by!(ib_post_id: 3_104_202)
post_3104202 = Domain::Post::InkbunnyPost.find_by!(ib_id: 3_104_202)
expect(post_3104202.title).to eq("Phantom Touch - Page 25")
expect(post_3104202.posted_at).to eq(
expect(post_3104202.posted_at).to be_within(1.second).of(
Time.parse("2023-08-27 21:31:40.365597+02"),
)
expect(post_3104202.creator).to eq(user_thendyart)
expect(post_3104202.last_file_updated_at).to eq(
expect(post_3104202.last_file_updated_at).to be_within(1.second).of(
Time.parse("2023-08-27 21:30:06.222262+02"),
)
expect(post_3104202.num_files).to eq(1)
expect(post_3104202.rating).to eq("adult")
expect(post_3104202.submission_type).to eq("comic")
expect(post_3104202.shallow_updated_at).to be_within(1.second).of(
Time.now,
Time.current,
)
expect(post_3104202.shallow_update_log_entry).to eq(log_entries[0])
expect(post_3104202.deep_updated_at).to be_nil
user_soulcentinel = Domain::Inkbunny::User.find_by!(ib_user_id: 349_747)
user_soulcentinel = Domain::User::InkbunnyUser.find_by!(ib_id: 349_747)
expect(user_soulcentinel.scanned_gallery_at).to be_nil
expect(
@@ -112,13 +112,13 @@ describe Domain::Inkbunny::Job::LatestPostsJob do
it "updates posts and files" do
expect { perform_now({}) }.to(
change(Domain::Inkbunny::Post, :count).by(1).and(
change(Domain::Inkbunny::User, :count).by(1),
change(Domain::Post::InkbunnyPost, :count).by(1).and(
change(Domain::User::InkbunnyUser, :count).by(1),
),
)
post_1047334 = Domain::Inkbunny::Post.find_by!(ib_post_id: 1_047_334)
post_1047334 = Domain::Post::InkbunnyPost.find_by!(ib_id: 1_047_334)
expect(post_1047334.title).to eq("Yellow Snake")
expect(post_1047334.last_file_updated_at).to eq(
expect(post_1047334.last_file_updated_at).to be_within(1.second).of(
Time.parse("2016-03-13 22:18:52.32319+01"),
)
@@ -129,15 +129,15 @@ describe Domain::Inkbunny::Job::LatestPostsJob do
# second perform should update the post
expect { perform_now({}) }.to(
change(Domain::Inkbunny::Post, :count)
change(Domain::Post::InkbunnyPost, :count)
.by(0)
.and(change(Domain::Inkbunny::User, :count).by(0))
.and(change(Domain::User::InkbunnyUser, :count).by(0))
.and(change(Domain::Inkbunny::File, :count).by(0)),
)
post_1047334.reload
expect(post_1047334.title).to eq("How to Photograph Snakes")
expect(post_1047334.last_file_updated_at).to eq(
expect(post_1047334.last_file_updated_at).to be_within(1.second).of(
Time.parse("2023-09-14 19:07:45.735562+02"),
)

View File

@@ -48,16 +48,16 @@ RSpec.describe Domain::Inkbunny::Job::UpdatePoolJob do
)
expect { perform_now({ pool: pool }) }.to change {
Domain::Inkbunny::Post.count
Domain::Post::InkbunnyPost.count
}.by(4)
# Verify posts were created correctly
post_3334290 = Domain::Inkbunny::Post.find_by!(ib_post_id: 3_334_290)
post_3334290 = Domain::Post::InkbunnyPost.find_by!(ib_id: 3_334_290)
expect(post_3334290.title).to eq("Phantom Touch: Follow-Up")
expect(post_3334290.posted_at).to eq(
expect(post_3334290.posted_at).to be_within(1.second).of(
Time.parse("2024-05-28 00:57:36.597545+00"),
)
expect(post_3334290.last_file_updated_at).to eq(
expect(post_3334290.last_file_updated_at).to be_within(1.second).of(
Time.parse("2024-05-28 00:55:24.811748+00"),
)
expect(post_3334290.num_files).to eq(1)
@@ -70,7 +70,7 @@ RSpec.describe Domain::Inkbunny::Job::UpdatePoolJob do
expect(post_3334290.deep_updated_at).to be_nil
# Verify user was created
user_thendyart = Domain::Inkbunny::User.find_by!(ib_user_id: 941_565)
user_thendyart = Domain::User::InkbunnyUser.find_by!(ib_id: 941_565)
expect(user_thendyart.name).to eq("ThendyArt")
# Verify UpdatePostsJob was enqueued for posts without deep_updated_at
@@ -94,7 +94,7 @@ RSpec.describe Domain::Inkbunny::Job::UpdatePoolJob do
pool.save!
expect { perform_now({ pool: pool }) }.not_to change {
Domain::Inkbunny::Post.count
Domain::Post::InkbunnyPost.count
}
expect(pool.deep_update_log_entry).to eq(log_entry)

View File

@@ -6,18 +6,19 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
before { Scraper::ClientFactory.http_client_mock = http_client_mock }
let(:avatar_url_str) { "https://example.com/avatar.jpg" }
let(:avatar_state) { nil }
let(:existing_avatar_sha256) { nil }
let(:avatar_state) { "pending" }
let(:existing_downloaded_at) { nil }
let(:existing_log_entry_id) { nil }
let!(:user) do
let(:user) { create(:domain_user_inkbunny_user) }
let!(:avatar) do
create(
:domain_inkbunny_user,
avatar_url_str: avatar_url_str,
avatar_state: avatar_state,
avatar_file_sha256: existing_avatar_sha256,
avatar_downloaded_at: existing_downloaded_at,
avatar_file_log_entry_id: existing_log_entry_id,
:domain_user_avatar,
user: user,
url_str: avatar_url_str,
state: avatar_state,
downloaded_at: existing_downloaded_at,
last_log_entry_id: existing_log_entry_id,
log_entry_id: existing_log_entry_id,
)
end
@@ -30,10 +31,9 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
it "logs a warning and returns" do
perform_job
user.reload
expect(user.avatar_state).to be_nil
expect(user.avatar).to be_nil
expect(user.avatar_log_entry).to be_nil
avatar.reload
expect(avatar.state).to eq("pending")
expect(avatar.log_entry).to be_nil
end
end
@@ -57,14 +57,11 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
perform_job
user.reload
expect(user.avatar_state).to eq("ok")
expect(user.avatar_file_sha256).to eq(log_entries[0].response_sha256)
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(log_entries[0].response_sha256)
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_downloaded_at).to be_within(1.second).of(
expect(avatar.state).to eq("ok")
expect(avatar.downloaded_at).to be_within(1.second).of(
log_entries[0].created_at,
)
expect(avatar.log_entry).to eq(log_entries[0])
end
context "when previous file exists" do
@@ -73,7 +70,6 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
let(:existing_blob_entry) do
create(:blob_entry, content: "previous", content_type: "image/jpeg")
end
let(:existing_avatar_sha256) { existing_blob_entry.sha256 }
let(:avatar_state) { :ok }
let(:existing_downloaded_at) { 1.day.ago }
@@ -81,17 +77,11 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
perform_job
user.reload
expect(user.avatar_state).to eq("ok")
expect(user.avatar_file_sha256).to eq(log_entries[0].response_sha256)
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(log_entries[0].response_sha256)
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_state_detail["log_entries"]).to eq(
[existing_log_entry.id, log_entries[0].id],
)
expect(user.avatar_downloaded_at).to be_within(1.second).of(
expect(avatar.state).to eq("ok")
expect(avatar.downloaded_at).to be_within(1.second).of(
log_entries[0].created_at,
)
expect(avatar.log_entry).to eq(log_entries[0])
end
end
end
@@ -116,12 +106,10 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
perform_job
user.reload
expect(user.avatar_state).to eq("not_found")
expect(user.avatar_file_sha256).to be_nil
expect(user.avatar).to be_nil
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_state_detail["log_entries"]).to eq([log_entries[0].id])
expect(user.avatar_downloaded_at).to be_nil
expect(avatar.state).to eq("file_404")
expect(avatar.downloaded_at).to be_nil
expect(avatar.last_log_entry).to eq(log_entries[0])
expect(avatar.log_entry).to be_nil
end
context "when previous file exists" do
@@ -130,7 +118,6 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
let(:existing_blob_entry) do
create(:blob_entry, content: "previous", content_type: "image/jpeg")
end
let(:existing_avatar_sha256) { existing_blob_entry.sha256 }
let(:avatar_state) { :ok }
let(:existing_downloaded_at) { 1.day.ago }
@@ -138,22 +125,17 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
perform_job
user.reload
expect(user.avatar_state).to eq("not_found")
expect(user.avatar_file_sha256).to eq(existing_blob_entry.sha256)
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(existing_blob_entry.sha256)
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_state_detail["log_entries"]).to eq(
[existing_log_entry.id, log_entries[0].id],
)
expect(user.avatar_downloaded_at).to be_within(1.second).of(
expect(avatar.state).to eq("file_404")
expect(avatar.downloaded_at).to be_within(1.second).of(
existing_downloaded_at,
)
expect(avatar.last_log_entry).to eq(log_entries[0])
expect(avatar.log_entry).to eq(existing_log_entry)
end
end
end
context "when avatar download fails with error" do
context "when avatar download fails and then succeeds" do
let! :log_entries do
HttpClientMockHelpers.init_http_client_mock(
http_client_mock,
@@ -176,29 +158,25 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
)
end
it "sets error state and raises fatal error" do
it "sets error state and then ok state" do
perform_now({ user: user }, should_raise: Scraper::JobBase::JobError)
user.reload
expect(user.avatar_state).to eq("error")
expect(user.avatar_state_detail["download_error"]).to include("500")
expect(user.avatar).to be_nil
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_state_detail["log_entries"]).to eq([log_entries[0].id])
avatar.reload
expect(avatar.state).to eq("http_error")
expect(avatar.last_log_entry).to eq(log_entries[0])
expect(avatar.log_entry).to be_nil
expect(avatar.error_message).to include("500")
perform_now({ user: user })
user.reload
expect(user.avatar_state).to eq("ok")
expect(user.avatar_state_detail["download_error"]).to be_nil
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(log_entries[1].response_sha256)
expect(user.avatar_log_entry).to eq(log_entries[1])
expect(user.avatar_state_detail["log_entries"]).to eq(
log_entries.map(&:id),
)
expect(user.avatar_downloaded_at).to be_within(1.second).of(
log_entries[1].created_at,
)
avatar.reload
expect(avatar.state).to eq("ok")
expect(avatar.error_message).to be_nil
expect(avatar).to be_present
expect(avatar.last_log_entry).to eq(log_entries[1])
expect(avatar.log_entry).to eq(log_entries[1])
end
context "when previous file exists" do
@@ -207,7 +185,6 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
let(:existing_blob_entry) do
create(:blob_entry, content: "previous", content_type: "image/jpeg")
end
let(:existing_avatar_sha256) { existing_blob_entry.sha256 }
let(:avatar_state) { :ok }
let(:existing_downloaded_at) { 1.day.ago }
@@ -215,31 +192,25 @@ describe Domain::Inkbunny::Job::UserAvatarJob do
perform_now({ user: user }, should_raise: Scraper::JobBase::JobError)
user.reload
expect(user.avatar_state).to eq("error")
expect(user.avatar_file_sha256).to eq(existing_blob_entry.sha256)
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(existing_blob_entry.sha256)
expect(user.avatar_log_entry).to eq(log_entries[0])
expect(user.avatar_state_detail["log_entries"]).to eq(
[existing_log_entry.id, log_entries[0].id],
)
expect(user.avatar_downloaded_at).to be_within(1.second).of(
avatar.reload
expect(avatar.state).to eq("http_error")
expect(avatar.last_log_entry).to eq(log_entries[0])
expect(avatar.log_entry).to eq(existing_log_entry)
expect(avatar.error_message).to include("500")
expect(avatar.downloaded_at).to be_within(1.second).of(
existing_downloaded_at,
)
perform_now({ user: user })
user.reload
expect(user.avatar_state).to eq("ok")
expect(user.avatar_state_detail["download_error"]).to be_nil
expect(user.avatar).to be_present
expect(user.avatar.sha256).to eq(log_entries[1].response_sha256)
expect(user.avatar_log_entry).to eq(log_entries[1])
expect(user.avatar_state_detail["log_entries"]).to eq(
[existing_log_entry.id] + log_entries.map(&:id),
)
expect(user.avatar_downloaded_at).to be_within(1.second).of(
log_entries[1].created_at,
)
avatar.reload
expect(avatar.state).to eq("ok")
expect(avatar.error_message).to be_nil
expect(avatar).to be_present
expect(avatar.last_log_entry).to eq(log_entries[1])
expect(avatar.log_entry).to eq(log_entries[1])
end
end
end

View File

@@ -2,8 +2,9 @@
require "rails_helper"
RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
# User ID is for `zaush`, set the wrong name to test that the name is updated
let!(:user) do
create(:domain_inkbunny_user, name: "the_user", ib_user_id: 26_540)
create(:domain_user_inkbunny_user, name: "the_user", ib_id: 26_540)
end
let(:args) { { user: user, caused_by_entry: nil } }
let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
@@ -37,7 +38,7 @@ RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
)
expect { perform_now(args) }.to(
change(Domain::Inkbunny::Post, :count)
change(Domain::Post::InkbunnyPost, :count)
.by(0)
.and(change(Domain::Inkbunny::File, :count).by(0))
.and(change(Domain::Inkbunny::User, :count).by(0)),
@@ -79,10 +80,10 @@ RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
it "fetches all pages and enqueues update jobs" do
expect { perform_now(args) }.to(
change(Domain::Inkbunny::Post, :count)
change(Domain::Post::InkbunnyPost, :count)
.by(4)
.and(change(Domain::Inkbunny::File, :count).by(0))
.and(change(Domain::Inkbunny::User, :count).by(0)),
.and(change(Domain::PostFile::InkbunnyPostFile, :count).by(0))
.and(change(Domain::User::InkbunnyUser, :count).by(0)),
)
user.reload
@@ -91,7 +92,7 @@ RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
expect(user.posts.count).to eq(4)
expect(user.shallow_update_log_entry).to eq(log_entries[0])
post_3507105 = user.posts.find_by(ib_post_id: 350_7105)
post_3507105 = user.posts.find_by(ib_id: 350_7105)
expect(post_3507105).to be_present
expect(post_3507105.num_files).to eq(5)
expect(post_3507105.files.count).to eq(0)
@@ -121,13 +122,13 @@ RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
end
let(:user) do
create(:domain_inkbunny_user, name: "the_user", ib_user_id: 16_532)
create(:domain_user_inkbunny_user, name: "the_user", ib_id: 16_532)
end
let(:args) { { user: user, caused_by_entry: nil } }
it "correctly handles posts with a null last_file_update_datetime" do
expect { perform_now(args) }.to(
change(Domain::Inkbunny::Post, :count).by(1),
change(Domain::Post::InkbunnyPost, :count).by(1),
)
user.reload
@@ -136,7 +137,7 @@ RSpec.describe Domain::Inkbunny::Job::UserGalleryJob do
expect(user.posts.count).to eq(1)
expect(user.shallow_update_log_entry).to eq(log_entries[0])
post_2855990 = user.posts.find_by(ib_post_id: 2_855_990)
post_2855990 = user.posts.find_by(ib_id: 2_855_990)
expect(post_2855990).to be_present
expect(post_2855990.num_files).to eq(0)
expect(post_2855990.files.count).to eq(0)